]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
WIP, sync with trunk to pickup 22 weeks of shell library enhancements.
authorlarrybr <larrybr@noemail.net>
Mon, 8 May 2023 21:35:07 +0000 (21:35 +0000)
committerlarrybr <larrybr@noemail.net>
Mon, 8 May 2023 21:35:07 +0000 (21:35 +0000)
FossilOrigin-Name: 00d0d5b10556bcc4ffb8d6083669134a913bc6ae7fcc787af5b1c292923354e8

17 files changed:
1  2 
Makefile.in
Makefile.msc
configure
configure.ac
main.mk
manifest
manifest.uuid
src/shell.c.in
test/permutations.test
test/releasetest_data.tcl
test/shell1.test
test/shell2.test
test/shell3.test
test/shell4.test
test/shell5.test
test/tester.tcl
test/wapptest.tcl

diff --cc Makefile.in
index 8e095174a6e82f069f632b89bebfe71b4336c68d,61cb3ef0e1b70330ae856ee466ea45e5aa3abb25..1a1e18ace83f2a90b305ddd491919fc342d397f9
@@@ -640,22 -600,52 +620,55 @@@ SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTA
  SHELL_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB
  SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC
  FUZZERSHELL_OPT = 
- FUZZCHECK_OPT = -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ
- FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000
- FUZZCHECK_OPT += -DSQLITE_PRINTF_PRECISION_LIMIT=1000
- FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS4
- FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS3_PARENTHESIS
- FUZZCHECK_OPT += -DSQLITE_ENABLE_FTS5
- FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE
- FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY
- FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB
- FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB
- FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c
+ FUZZCHECK_OPT += -I$(TOP)/test
+ FUZZCHECK_OPT += -I$(TOP)/ext/recover
+ FUZZCHECK_OPT += \
+   -DSQLITE_OSS_FUZZ \
+   -DSQLITE_ENABLE_BYTECODE_VTAB \
+   -DSQLITE_ENABLE_DBPAGE_VTAB \
+   -DSQLITE_ENABLE_DBSTAT_VTAB \
+   -DSQLITE_ENABLE_BYTECODE_VTAB \
+   -DSQLITE_ENABLE_DESERIALIZE \
+   -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
+   -DSQLITE_ENABLE_FTS3_PARENTHESIS \
+   -DSQLITE_ENABLE_FTS4 \
+   -DSQLITE_ENABLE_FTS5 \
+   -DSQLITE_ENABLE_GEOPOLY \
+   -DSQLITE_ENABLE_MATH_FUNCTIONS \
+   -DSQLITE_ENABLE_MEMSYS5 \
+   -DSQLITE_ENABLE_NORMALIZE \
+   -DSQLITE_ENABLE_OFFSET_SQL_FUNC \
+   -DSQLITE_ENABLE_PREUPDATE_HOOK \
+   -DSQLITE_ENABLE_RTREE \
+   -DSQLITE_ENABLE_SESSION \
+   -DSQLITE_ENABLE_STMTVTAB \
+   -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \
+   -DSQLITE_ENABLE_STAT4 \
+   -DSQLITE_ENABLE_STMT_SCANSTATUS \
+   -DSQLITE_MAX_MEMORY=50000000 \
+   -DSQLITE_MAX_MMAP_SIZE=0 \
+   -DSQLITE_OMIT_LOAD_EXTENSION \
+   -DSQLITE_PRINTF_PRECISION_LIMIT=1000 \
+   -DSQLITE_PRIVATE=""
+ FUZZCHECK_SRC += $(TOP)/test/fuzzcheck.c
+ FUZZCHECK_SRC += $(TOP)/test/ossfuzz.c
+ FUZZCHECK_SRC += $(TOP)/test/fuzzinvariants.c
+ FUZZCHECK_SRC += $(TOP)/ext/recover/dbdata.c
+ FUZZCHECK_SRC += $(TOP)/ext/recover/sqlite3recover.c
+ FUZZCHECK_SRC += $(TOP)/test/vt02.c
  DBFUZZ_OPT =
+ ST_OPT = -DSQLITE_OS_KV_OPTIONAL
+ # In wasi-sdk builds, disable the CLI shell build in the "all" target.
+ SQLITE3_SHELL_TARGET_  = sqlite3$(TEXE)
+ SQLITE3_SHELL_TARGET_1 =
+ SQLITE3_SHELL_TARGET   = $(SQLITE3_SHELL_TARGET_@HAVE_WASI_SDK@)
  
 +#
 +SHELL_OPT_NOEXT = $(SHELL_OPT) -DSHELL_OMIT_EXTENSIONS=7
 +
  # This is the default Makefile target.  The objects listed here
  # are what get build when you type just "make" with no arguments.
  #
@@@ -1112,33 -1113,37 +1141,34 @@@ keywordhash.h:       $(TOP)/tool/mkkeywordhas
        $(BCC) -o mkkeywordhash$(BEXE) $(OPT_FEATURE_FLAGS) $(OPTS) $(TOP)/tool/mkkeywordhash.c
        ./mkkeywordhash$(BEXE) >keywordhash.h
  
++$(TOP)/ext/misc/basexx.c: $(TOP)/ext/misc/base64.c $(TOP)/ext/misc/base85.c
++
  # Source files that go into making shell.c
  SHELL_SRC = \
 -      $(TOP)/src/shell.c.in \
 +        $(TOP)/src/shell.c.in \
++        $(TOP)/ext/expert/sqlite3expert.c \
++        $(TOP)/ext/expert/sqlite3expert.h \
          $(TOP)/ext/misc/appendvfs.c \
 -      $(TOP)/ext/misc/completion.c \
 -        $(TOP)/ext/misc/decimal.c \
+         $(TOP)/ext/misc/basexx.c \
 -        $(TOP)/ext/misc/base64.c \
 -        $(TOP)/ext/misc/base85.c \
 -      $(TOP)/ext/misc/fileio.c \
 +        $(TOP)/ext/misc/completion.c \
 +        $(TOP)/ext/misc/decimal.c \
 +        $(TOP)/ext/misc/fileio.c \
          $(TOP)/ext/misc/ieee754.c \
          $(TOP)/ext/misc/regexp.c \
          $(TOP)/ext/misc/series.c \
 -      $(TOP)/ext/misc/shathree.c \
 -      $(TOP)/ext/misc/sqlar.c \
 +        $(TOP)/ext/misc/shathree.c \
 +        $(TOP)/ext/misc/sqlar.c \
          $(TOP)/ext/misc/uint.c \
-         $(TOP)/ext/expert/sqlite3expert.c \
-         $(TOP)/ext/expert/sqlite3expert.h \
-         $(TOP)/ext/misc/zipfile.c \
 -      $(TOP)/ext/expert/sqlite3expert.c \
 -      $(TOP)/ext/expert/sqlite3expert.h \
 -      $(TOP)/ext/misc/zipfile.c \
 -      $(TOP)/ext/misc/memtrace.c \
 -      $(TOP)/ext/recover/dbdata.c \
 -      $(TOP)/ext/recover/sqlite3recover.c \
 -      $(TOP)/ext/recover/sqlite3recover.h \
 +        $(TOP)/ext/misc/memtrace.c \
-         $(TOP)/src/test_windirent.c \
 +        $(TOP)/src/shext_linkage.h \
 +        $(TOP)/src/obj_interfaces.h
++        $(TOP)/ext/recover/dbdata.c \
++        $(TOP)/ext/misc/zipfile.c \
+         $(TOP)/src/test_windirent.c
  
  shell.c:      $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl
 -      $(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c
 -
 -
 -
 +      $(MKSHELL_TOOL) >shell.c
  
  # Rules to build the extension objects.
  #
  icu.lo:       $(TOP)/ext/icu/icu.c $(HDR) $(EXTHDR)
diff --cc Makefile.msc
Simple merge
diff --cc configure
Simple merge
diff --cc configure.ac
Simple merge
diff --cc main.mk
Simple merge
diff --cc manifest
index 09b0d04923101b8c5c58352eb99005ed5be77a64,02343baa6b7e0a3951264c3ebeae33ed3b3c220b..c06480f18ccef6cf8328a7999683a6dd70c56ac2
+++ b/manifest
@@@ -1,13 -1,13 +1,13 @@@
- C WIP,\spre-sync-to-trunk\scheck-in\sto\scapture\sextensive\schanges\sto\sshell\ssource.\s(WASM\sand\susual\sshell\stweaks)
- D 2022-12-18T10:27:43.328
 -C Fix\srecover1.test\sso\sthat\sit\sworks\swith\sDEFAULT_AUTOVACUUM\sbuilds.
 -D 2023-05-08T16:26:22.900
++C WIP,\ssync\swith\strunk\sto\spickup\s22\sweeks\sof\sshell\slibrary\senhancements.
++D 2023-05-08T21:35:07.711
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
- F Makefile.in 3a2d4e3a13c497b24b92fa36e93f80dc49844f1ca62e5a7848b899afe7105a37
 -F Makefile.in 764f2e3e8fb4ae1c8dfe03e65b2b3b01bd1fc57edf78ec2cab3a1301e90e1905
++F Makefile.in 07c0feff2b9b50762c3db2413c5d2f67d97c96c4420dbe0e91f7e6dfaeb4bcc6
  F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
- F Makefile.msc 9610cfe44d83d430ac7db9146678447940fb582a30a8f4b6902ae3c2be1c5119
- F README.md 2dd87a5c1d108b224921f3dd47dea567973f706e1f6959386282a626f459a70c
- F VERSION fa8e7d2d1cc962f9e14c6d410387cf75860ee139462763fda887c1be4261f824
 -F Makefile.msc ada3466f8f0112a8baead4d6cc2a99bf544d228958baae12ca35a3ee5755c806
++F Makefile.msc 997ff8f34c6e8ec157d2b61e73cf4e1d2d5bcb9ffc6f9af068f4fd4deef0eebf
+ F README.md e05bd8fcb45da04ab045c37f79a98654e8aa3b3b8f302cfbba80a0d510df75f7
+ F VERSION 17f95ae2fdf21f0e9575eb0b0511ea63f15d71dfff431b21c2b4adbfa70cfbbf
  F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
  F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2
  F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90
@@@ -32,10 -32,9 +32,9 @@@ F autoconf/tea/win/makefile.vc 2c478a9a
  F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea00342d1a1a7eaa19cb
  F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63
  F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6
- F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc
  F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559
- F configure f03fac8b39d72d4f5bf3df81e03195cca31b169add8e9ef1b55047cba3285e2d x
- F configure.ac f8e54c0c89f5c96fb073528afa53b41ff4e6a91b04d893dc0bce433ff8c84f91
 -F configure 2f2c090e0a1d051bb53741f0c961f5ebb24507a4f599d686ad6d7ff236144609 x
 -F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8
++F configure 5e52f3bce3b598d0c4ee6e6c92c180f35f9b29dfcc1775332bf87cf55302f9c3 x
++F configure.ac 510be9293c7efca69c0cc7f427f223b0597f82dda214af7491887db25fa4e237
  F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
  F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd
  F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f
@@@ -323,14 -307,13 +309,14 @@@ F ext/misc/regexp.c f50ab59bfa8934b7ed9
  F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c
  F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c
  F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946
- F ext/misc/series.c 8d79354f2c3d46b95ee21272a07cf0bcabb58d1f2b06d9e7b8a31dca1dacb3e5
+ F ext/misc/series.c 37d27377684d3ea14177540d2f2767163197611eaba905790c96abd4ab552cd3
  F ext/misc/sha1.c 4011aef176616872b2a0d5bccf0ecfb1f7ce3fe5c3d107f3a8e949d8e1e3f08d
- F ext/misc/shathree.c 7b17615869a495659f1569ada1d8d3d21b4a24614f2746d93cc87ef7c0b6b36d
+ F ext/misc/shathree.c 543af7ce71d391cd3a9ab6924a6a1124efc63211fd0f2e240dc4b56077ba88ac
  F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
  F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f
- F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6
- F ext/misc/stmt.c 35063044a388ead95557e4b84b89c1b93accc2f1c6ddea3f9710e8486a7af94a
+ F ext/misc/sqlar.c 53e7d48f68d699a24f1a92e68e71eca8b3a9ff991fe9588c2a05bde103c6e7b7
+ F ext/misc/stmt.c bc30d60d55e70d0133f10ac6103fe9336543f673740b73946f98758a2bb16dd7
 +F ext/misc/tclshext.c.in d288a729637bf8d55497b72f0cefeec09c3a3ab9c5826f75ab7597b04ba6e523
  F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4
  F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b
  F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b
@@@ -342,53 -325,72 +328,72 @@@ F ext/misc/vfsstat.c 474d08efc697b8eba3
  F ext/misc/vtablog.c 5538acd0c8ddaae372331bee11608d76973436b77d6a91e8635cfc9432fba5ae
  F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd
  F ext/misc/wholenumber.c a838d1bea913c514ff316c69695efbb49ea3b8cb37d22afc57f73b6b010b4546
- F ext/misc/zipfile.c 22afe121d1a5e318453b7cdbc0f5492161d2fd4fce548ff3605da05e89be7140
+ F ext/misc/zipfile.c b1f36004c19fb5f949fb166fc4ab88e96a86f66629e9ddb4736a45b63fc3d553
  F ext/misc/zorder.c b0ff58fa643afa1d846786d51ea8d5c4b6b35aa0254ab5a82617db92f3adda64
  F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
- F ext/rbu/rbu1.test c62904bd9526dcdc3496a21199aaf14ae191bbadbf67f076bf16be6b3f2115c2
- F ext/rbu/rbu10.test 06d2bc934a03a0978e750cc9c95b419d9b0bcbec1fc77128e33e377c3a73240b
- F ext/rbu/rbu11.test 5c834cf491086b45e071eabf71f708febc143e86a384a92de69e0b1a4cace144
- F ext/rbu/rbu12.test 29f8b2118f6c96fac3755bd6d2b55c2db24f878b1f11fbfbe294f3a230a3dcdc
- F ext/rbu/rbu13.test 1285298e3360ec74511764841b3c174dcfe21da2f618c22febf1a20abd0365c2
- F ext/rbu/rbu14.test 4a7bf0b3a4516d3ab0bc0ba4ceb53eb7e3324147ccda152e561060f659dbba31
- F ext/rbu/rbu3.test d6c6cc7a1326e8e23b9820f30bd3054f22092e503fadfd2a660ae006653f6d80
- F ext/rbu/rbu5.test 724b38ea5f722e3d22dc76343860bd998bb676c3f78c4bc8175df5c5d7720e23
- F ext/rbu/rbu6.test 401064236d3cf86b7edc01c586d7c5554f48553946fbfa1a3af35d7e47dce9e3
- F ext/rbu/rbu7.test ae25f47b56f178197fc1098537a35a39176cc73d1629b03dc9d795929fc36ec2
- F ext/rbu/rbu8.test b98a6fc58ead84a0e6ddee775b9702cd981f318d5d4fd1d4df0fa0c40db7251b
- F ext/rbu/rbu9.test 0e4d985e25620d61920597e8ea69c871c9e8c1f5a0be2ae9fa70bb641d74378c
- F ext/rbu/rbuA.test b34a90cb495682c25b5fc03a9d5e7a4fc99541c29256f25e2e2a4f6542b4f5b3
- F ext/rbu/rbuB.test 52b07158824c6927b7e25554ace92a695cdebfc296ae3d308ac386984aded9bc
- F ext/rbu/rbuC.test 80f1cc2fb74f44b1128fd0ed8eedab3a76fefeb72a947860e2869ef76fc8dc6b
- F ext/rbu/rbu_common.tcl 60d904133ff843fe72cc0514e9dd2486707181e6e0fbab20979da28c48d21de9
- F ext/rbu/rbubusy.test f38ef557358564491b8a2ee70e4cad31e40fcea57a16f27bc56ba40a59bbde50
- F ext/rbu/rbucollate.test cac528a9a46318cba42e61258bb42660bbbf4fdb9a8c863de5a54ad0c658d197
- F ext/rbu/rbucrash.test 000981a1fe8a6e4d9a684232f6a129e66a3ef595f5ed74655e2f9c68ffa613b4
- F ext/rbu/rbucrash2.test efa143cc94228eb0266d3f1abfbee60a5838a84cef7cc3fcb8c145b74d96fd41
- F ext/rbu/rbudiff.test abe895a8d479e4d33acb40e244e3d8e2cd25f55a18dfa8b9f83e13d00073f600
- F ext/rbu/rbudor.test e3e8623926012f43eebe51fedf06a102df2640750d971596b052495f2536db20
- F ext/rbu/rbuexlock.test 4634a5526d02bf80b0c563f95774bd5af5783e3219ddeb30e413753c9a65510c
- F ext/rbu/rbuexpr.test 10d0420537c3bc7666e576d72adeffe7e86cfbb00dcc30aa9ce096c042415190
- F ext/rbu/rbufault.test 2d7f567b79d558f6e093c58808cab4354f8a174e3802f69e7790a9689b3c09f8
- F ext/rbu/rbufault2.test c81327a3ac2c385b9b954db3644d4e0df93eeebfc3de9f1f29975a1e73fd3d0c
- F ext/rbu/rbufault3.test b2fcc9db5c982b869f67d1d4688d8cb515d5b92f58011fff95665f2e62cec179
- F ext/rbu/rbufault4.test 03d2849c3df7d7bd14a622e789ff049e5080edd34a79cd432e01204db2a5930a
- F ext/rbu/rbufts.test 0ae8d1da191c75bd776b86e24456db0fb6e97b7c944259fae5407ea55d23c31d
- F ext/rbu/rbumisc.test 329986cf5dd51890c4eb906c2f960ebb773a79a64bed90f506b7c417825b37eb
- F ext/rbu/rbumulti.test 5fb139058f37ddc5a113c5b93238de915b769b7792de41b44c983bc7c18cf5b9
- F ext/rbu/rbupartial.test f25df014b8dbe3c5345851fba6e66f79ab237f57dc201b2d5f0dbae658ae5a4c
- F ext/rbu/rbuprogress.test 857cf1f8166c83ef977edb9ef4fc42d80f71fbd798652b46ae2f3a7031870f8d
- F ext/rbu/rburesume.test dbdc4ca504e9c76375a69e5f0d91205db967dcc509a5166ca80231f8fda49eb1
- F ext/rbu/rbusave.test f4190a1a86fccf84f723af5c93813365ae33feda35845ba107b59683d1cdd926
- F ext/rbu/rbusplit.test b37e7b40b38760881dc9c854bd40b4744c6b6cd74990754eca3bda0f407051e8
- F ext/rbu/rbutemplimit.test 05ceefa90a2e26a99f40dd48282ed63a00df5e59c1f2bfd479c143e201a1b0ba
- F ext/rbu/rbuvacuum.test 55e101e90168c2b31df6c9638fe73dc7f7cc666b6142266d1563697d79f73534
- F ext/rbu/rbuvacuum2.test 886add83fd74bcb02e6dd016ae5b585367bd58c5d0694c9d9ca7bdb1d1f578c2
- F ext/rbu/rbuvacuum3.test 8addd82e4b83b4c93fa47428eae4fd0dbf410f8512c186f38e348feb49ba03dc
- F ext/rbu/rbuvacuum4.test a78898e438a44803eb2bc897ba3323373c9f277418e2d6d76e90f2f1dbccfd10
- F ext/rbu/sqlite3rbu.c 8737cabdfbee84bb25a7851ecef8b1312be332761238da9be6ddb10c62ad4291
- F ext/rbu/sqlite3rbu.h 1dc88ab7bd32d0f15890ea08d23476c4198d3da3056985403991f8c9cd389812
- F ext/rbu/test_rbu.c 03f6f177096a5f822d68d8e4069ad8907fe572c62ff2d19b141f59742821828a
+ F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255
+ F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363
+ F ext/rbu/rbu11.test 8584f80ef4be00e6beec4154f638847ffc40b5f2832ffadfbaf558ae40e50cb5
+ F ext/rbu/rbu12.test ec63aa7bfc3c65c1d774bf4357ed731723827d211d9d7cb0efa171bbaeeebaf4
+ F ext/rbu/rbu13.test 658edbc3325d79252a98b761fde95460e439f80e820ff29e10261e25f870b3b6
+ F ext/rbu/rbu14.test 05dac607a62f62102f4db92135979a8a4501143638060019aca08c753822cf39
+ F ext/rbu/rbu3.test 4a81517af618c3bf8c72e2d0b81c7c06acb8d176036d63d8e6669b73342306ae
+ F ext/rbu/rbu5.test e21820b83822ae4c12afc2078a7b6c0523fb0cefe69c8b23c044cea91359e81c
+ F ext/rbu/rbu6.test db2ff1f832dfc9e34c7910b17e157c2fe0e36024a3fe1119dd6437640dc07c82
+ F ext/rbu/rbu7.test 5fa41734613a3ae1bb93d280eb3c341cff5dcc72652ff9ec7fbaa12425eda9c2
+ F ext/rbu/rbu8.test 93d45824dab8f68872b6d22acc787ab18ba92ef0fa0d430be37653d0246c7a0d
+ F ext/rbu/rbu9.test 4b66f0705442711a44b54ef2cc3c59952f1ea15f12e34442681bdb1a6eb33065
+ F ext/rbu/rbuA.test 3f8fdd4ae7b9a0571af7361cd88359254f63e445ac4acfe395173e31d7e3fc31
+ F ext/rbu/rbuB.test c639803bbc1dc9358afe6abe046dc4d3e9965238b75239b04e3a8e33e3e90f85
+ F ext/rbu/rbuC.test 5326ea3954754c68fd518beb70d3e6b6690af53e1a5fa102d650e4110b26b4c5
+ F ext/rbu/rbu_common.tcl 15d063397a89aeaf26b4cbdf6f29911b4154a902ba61a40c4f180ab452454a63
+ F ext/rbu/rbubusy.test 88298187ad35aac9084436d85ca66b3722f96eaa704a09cfe5f931d452ab7237
+ F ext/rbu/rbucollate.test 9852ec5e5ba7f3b04ce849a24ef7298e03ae0f16e58e6031d0f845234559feec
+ F ext/rbu/rbucrash.test d2b5d619d9281c89cad74401b73b46172daa89906940b1d739c813ddc0cd2cf5
+ F ext/rbu/rbucrash2.test 0a1a72223d880215ce2893a3260320c31a9358d23cb124c610e4f0d984a93285
+ F ext/rbu/rbudiff.test 8b8b8b569c68fc880134e0fac4bf6b4b7a907aea4cc6eacf7e1d45e1d47b6aac
+ F ext/rbu/rbudor.test 293a192e668bb8e9c7c9704b080c1086ee17496f768e0f1823049e7d02651d1b
+ F ext/rbu/rbuexlock.test e07a0875d0b72f7c007e5d5dcf424e9d48a4752a1a9bcee8ee36947e6add6d5b
+ F ext/rbu/rbuexpr.test 2c91617509c88b6e9030f7bf6ff720df26032fcd801adc25533feae726a57382
+ F ext/rbu/rbufault.test c51de14067cfe867849530d3d1718ffeb28522f28d52937f95dd7bc2116eb42e
+ F ext/rbu/rbufault2.test 8cc8f6298d2d7d20080b2c77e65b607af8b89839f9d87c0972b27e6442edc258
+ F ext/rbu/rbufault3.test d14ff46e050816ce43c4ed320a0927712636ac11bf48bfc5f74601f183af5445
+ F ext/rbu/rbufault4.test 39fbf093b7e16aae85dc309262ec570d217a1578538c1c74dd621e5451c083d6
+ F ext/rbu/rbufts.test df754d2f96c22d1da8b5d685b4a4a49863971920856d17620cef724e3a9b6edd
+ F ext/rbu/rbumisc.test 6641749e42c83062824c86b3d03a47f8ec35760f341bc023f53e612655b0a8af
+ F ext/rbu/rbumulti.test 6f6cdd9b3775108aada5216762cbbd7b5d5caa7cb620b3e6e1b8ace81286a2e0
+ F ext/rbu/rbupartial.test 4ed7789f47128c8aa7ff58445face8a070cef852993afe03c863913f3cea8729
+ F ext/rbu/rbupass.test 2ee86581a441f3b4b449b99a2dc203d5d6a08750dd2ee9ab6a02743e238d3c8a
+ F ext/rbu/rbuprogress.test db8bb26a8123d35f52acfc3984b56caa31c8fcd1fa3589991b9c8e8a68e64b59
+ F ext/rbu/rburename.test 8d8a6a6ba896338d0610658e1f60e8055a181d5913e1e21c41b866a8f15bb7cd
+ F ext/rbu/rburesume.test 1403752d152b55efb7fc25749c0fccc790061371ec9ffe428cc04f8a69bb834c
+ F ext/rbu/rbusave.test 588b618dad9d65c4b13d03a79931de82213503fedc26bdf5789c996ecf427fba
+ F ext/rbu/rbusplit.test a6dedd23cf37bcf2e8646d9d7139846e96d60d92f9bc6d6ba6ca8c24c0bd1f72
+ F ext/rbu/rbutemplimit.test 4980df2d4b74f4dd982add8f78809106154ef5a3c4bdce747422ab0b0481e029
+ F ext/rbu/rbuvacuum.test 542561741ff2b262e3694bc6012b44694ee62c545845319a06f323783b15311e
+ F ext/rbu/rbuvacuum2.test ae097d04feb041446a74fac94b24bffeb3fdd60e32b848c5611e507ab702b81b
+ F ext/rbu/rbuvacuum3.test 3ce42695fdf21aaa3499e857d7d4253bc499ad759bcd6c9362042c13cd37d8de
+ F ext/rbu/rbuvacuum4.test ffccd22f67e2d0b380d2889685742159dfe0d19a3880ca3d2d1d69eefaebb205
+ F ext/rbu/sqlite3rbu.c 71a7f0dea3a846ff7c2499dc34a2528f5ddcbe23e2c54dc3cd1fa4d933377c6d
+ F ext/rbu/sqlite3rbu.h 9d923eb135c5d04aa6afd7c39ca47b0d1d0707c100e02f19fdde6a494e414304
+ F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055
 -F ext/recover/dbdata.c 31d580785cf14eb3c20ed6fbb421a10a66569858f837928e6b326088c38d4c72
++F ext/recover/dbdata.c 31d580785cf14eb3c20ed6fbb421a10a66569858f837928e6b326088c38d4c72 w ext/misc/dbdata.c
+ F ext/recover/recover1.test c484d01502239f11b61f23c1cee9f5dd19fa17617f8974e42e74d64639c524cf
+ F ext/recover/recover_common.tcl a61306c1eb45c0c3fc45652c35b2d4ec19729e340bdf65a272ce4c229cefd85a
+ F ext/recover/recoverbuild.test c74170e0f7b02456af41838afeb5353fdb985a48cc2331d661bbabbca7c6b8e3
+ F ext/recover/recoverclobber.test 3ba6c0c373c5c63d17e82eced64c05c57ccaf26c1abe1ca7141334022a79f32e
+ F ext/recover/recovercorrupt.test 64c081ad1200ae77b447da99eb724785d6bf71715f394543dc7689642e92bf49
+ F ext/recover/recovercorrupt2.test 74bef7dd2d7dd4856f3da21be6e213d27da44827e0f5f0946ca0325b46d163ed
+ F ext/recover/recoverfault.test 9d9f88eeb222615a25e7514f234c950d46bee20d24cd8db49d8fff8d650dcfe1
+ F ext/recover/recoverfault2.test 730e7371bcda769554d15460cb23126abba1be8eca9539ccabf63623e7bb7e09
+ F ext/recover/recoverold.test 68db3d6f85dd2b98e785b6c4da4f5eea4bbe52ccf6674d9a94c7506dc92596aa
+ F ext/recover/recoverpgsz.test 3658ab8e68475b1bb87d6af88baa04551c84b73280a566a1be847182410ffc58
+ F ext/recover/recoverrowid.test f948bf4024a5f41b0e21b8af80c60564c5b5d78c05a8d64fc00787715ff9f45f
+ F ext/recover/recoverslowidx.test 5205a9742dd9490ee99950dabb622307355ef1662dea6a3a21030057bfd81411
+ F ext/recover/recoversql.test e66d01f95302a223bcd3fd42b5ee58dc2b53d70afa90b0d00e41e4b8eab20486
+ F ext/recover/sqlite3recover.c b3813090c97df34858bab8ece4e8c49d9e2f56ba2b9bb019f57f44bc234b3c6d
+ F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0e24ff9c74820959
+ F ext/recover/test_recover.c 1a34e2d04533d919a30ae4d5caeb1643f6684e9ccd7597ca27721d8af81f4ade
  F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15
  F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996
  F ext/repair/checkindex.c 4383e4469c21e5b9ae321d0d63cec53e981af9d7a6564be6374f0eeb93dfc890
@@@ -474,10 -478,83 +481,83 @@@ F ext/session/test_session.c 5285482f83
  F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
  F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
  F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb
+ F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c
+ F ext/wasm/GNUmakefile 38700d5074af690f004e4e5f3533164ab49693b9d0832929c4ecf97a0bc09494
+ F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576
+ F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9
+ F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab
+ F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see fb29e62082a658f0d81102488414d422c393c4b20cc2f685b216bc566237957b
+ F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
+ F ext/wasm/api/README.md 77a2f1f2fc60a35def7455dffc8d3f2c56385d6ac5c6cecc60fa938252ea2c54
+ F ext/wasm/api/extern-post-js.c-pp.js 393ab78b807da94096eae1a68bfddb999a2299936a185d910162fe87a57a9a3a
+ F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41
+ F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1
+ F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62
+ F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057afb08161d7511219
+ F ext/wasm/api/sqlite3-api-cleanup.js cc21e3486da748463e02bbe51e2464c6ac136587cdfd5aa00cd0b5385f6ca808
+ F ext/wasm/api/sqlite3-api-glue.js f1b2dcb944de5138bb5bd9a1559d2e76a4f3ec25260963d709e8237476688803
+ F ext/wasm/api/sqlite3-api-oo1.js 2691a34a741015127b210954a1b9586764d3ff0c8a20f00fd15c00f339ecc79f
+ F ext/wasm/api/sqlite3-api-prologue.js 17f4ec398ba34c5c666fea8e8c4eb82064a35b302f2f2eb355283cd8d3f68ed5
+ F ext/wasm/api/sqlite3-api-worker1.js 40a5b1813fcbe789f23ae196c833432c8c83e7054d660194ddfc51eab1c5b9bf
+ F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89
+ F ext/wasm/api/sqlite3-opfs-async-proxy.js 70914ae97784d3028150bbf252e07a423056c42cc345903c81b5fae661ce512f
+ F ext/wasm/api/sqlite3-v-helper.js e5c202a9ecde9ef818536d3f5faf26c03a1a9f5192b1ddea8bdabf30d75ef487
+ F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 89640e4874a60cb2d973306b272384ffb45c7915375c7bb0355c7586f88dc39c
+ F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
+ F ext/wasm/api/sqlite3-wasm.c 12a096d8e58a0af0589142bae5a3c27a0c7e19846755a1a37d2c206352fbedda
+ F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js 2710a06a59620c6bf7ce298ab1fb6c9ce825b9f9379728b74c486db6613beecc
+ F ext/wasm/api/sqlite3-worker1.c-pp.js da509469755035e919c015deea41b4514b5e84c12a1332e6cc8d42cb2cc1fb75
+ F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8
+ F ext/wasm/batch-runner.js 0dad6a02ad796f1003d3b7048947d275c4d6277f63767b8e685c27df8fdac93e
+ F ext/wasm/c-pp.c 6d80d8569d85713effe8b0818a3cf51dc779e3f0bf8dc88771b8998552ee25b4
+ F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b
+ F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15
+ F ext/wasm/common/testing.css 0ff15602a3ab2bad8aef2c3bd120c7ee3fd1c2054ad2ace7e214187ae68d926f
+ F ext/wasm/common/whwasmutil.js 749a1f81f85835e9a384e9706f2a955a7158f2b8cc9da33f41105cac7775a305
+ F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed
+ F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508
+ F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6
+ F ext/wasm/demo-jsstorage.html 409c4be4af5f207fb2877160724b91b33ea36a3cd8c204e8da1acb828ffe588e
+ F ext/wasm/demo-jsstorage.js 44e3ae7ec2483b6c511384c3c290beb6f305c721186bcf5398ca4e00004a06b8
+ F ext/wasm/demo-worker1-promiser.html 1de7c248c7c2cfd4a5783d2aa154bce62d74c6de98ab22f5786620b3354ed15f
+ F ext/wasm/demo-worker1-promiser.js 51b02509a109e82f623fb4c900c8b48b9a77cc13fbd038396f9a083b86593ae3
+ F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d
+ F ext/wasm/demo-worker1.js 2c7794d8bc4ab9ecf9cdc2c15de940b11a006942226e441ea41edd458dfc0a26
+ F ext/wasm/dist.make 451fb1b732257849f6e898d2a862512a0401500ed369ef53bdfeddf9c77bc3b9
+ F ext/wasm/example_extra_init.c 2347cd69d19d839ef4e5e77b7855103a7fe3ef2af86f2e8c95839afd8b05862f
+ F ext/wasm/fiddle.make dbe36b90b8907ae28ecb9c0e9fd8389dbdaecf117ea4fb2ea33864bdfa498a94
+ F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
+ F ext/wasm/fiddle/fiddle-worker.js 163d6139a93fab4bcb72064923df050d4e7c0ff0d8aa061ce8776a6e75da8a10
+ F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5653284071715
+ F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2
+ F ext/wasm/index-dist.html 22379774f0ad4edcaaa8cf9c674c82e794cc557719a8addabed74eb8069d412e
+ F ext/wasm/index.html dd900891844caebd9cadbddd704f66bd841d7c12fd69ce5af490e2c10fb49f45
+ F ext/wasm/jaccwabyt/jaccwabyt.js 8287c0537fa0750414edbe75ce64668a81c8716df5ec4c3e6bb4f11bd1c36031
+ F ext/wasm/jaccwabyt/jaccwabyt.md 37911f00db12cbcca73aa1ed72594430365f30aafae2fa9c886961de74e5e0eb
+ F ext/wasm/module-symbols.html 841de62fc198988b8330e238c260e70ec93028b096e1a1234db31b187a899d10
+ F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06
+ F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18
+ F ext/wasm/speedtest1-wasmfs.html 7a301f4f5b6ad4f5d37fd6e7ca03a2f5d5547fd289da60a39075a93d7646d354
+ F ext/wasm/speedtest1-worker.html 82869822e641c1bef3ec0cd2d7d2b6a42d0b4f68a7b160fb2e1dd0b523940a9b
+ F ext/wasm/speedtest1-worker.js 13b57c4a41729678a1194014afec2bd5b94435dcfc8d1039dfa9a533ac819ee1
+ F ext/wasm/speedtest1.html ff048b4a623aa192e83e143e48f1ce2a899846dd42c023fdedc8772b6e3f07da
+ F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x
+ F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0
+ F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5
+ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f
+ F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b
+ F ext/wasm/tester1-worker.html 258d08f1ba9cc2d455958751e26be833893cf9ff7853e9436e593e1f778a386b
+ F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2
+ F ext/wasm/tester1.c-pp.js 587a18db0f794c594eb0fade37c92af3e2370501576bb08dafd8ed7d009b6516
+ F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1
+ F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d
+ F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
+ F ext/wasm/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5
+ F ext/wasm/wasmfs.make cf9a68162d92ca2bcb0b9528b244cb36d5cc2d84ccc9c2d398461927d6e75aea
  F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
  F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
- F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
- F main.mk b685af8628262105cdc7173ca7d6e4803eb8186069c9d51fcb3089cfc9d96a52
+ F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0
 -F main.mk ac6b13f8ecc43f377e9912380ea4cf366051d7f784cf61c8886e03e1cf0fbefa
++F main.mk 3d5e743f92a3ad84ccd4299245727ce1e2ece646c1bec51482ee16392ad9bba8
  F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
  F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
  F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@@@ -489,39 -566,40 +569,40 @@@ F spec.template 86a4a43b99ebb3e75e6b9a7
  F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
  F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786
  F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
- F src/alter.c 9395ece9850ad57c6fbb453aeb5185be4bae3b159c4b37611425c565124ee849
- F src/analyze.c aabdf3769c7fd9954a8ec508eb7041ae174b66f88d12c47199fabbea9a646467
- F src/attach.c 4431f82f0247bf3aaf91589acafdff77d1882235c95407b36da1585c765fbbc8
 -F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2
++F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2 w config.h.in
+ F src/alter.c 482c534877fbb543f8295992cde925df55443febac5db5438d5aaba6f78c4940
+ F src/analyze.c a1f3061af16c99f73aed0362160176c31a6452de1b02ada1d68f6839f2a37df0
+ F src/attach.c cc9d00d30da916ff656038211410ccf04ed784b7564639b9b61d1839ed69fd39
  F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf
- F src/backup.c a2891172438e385fdbe97c11c9745676bec54f518d4447090af97189fd8e52d7
+ F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523
  F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d
- F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6
- F src/btree.c df695e953c2ce78ce4878ee5016751df9bc9a921dc0500a0f53ab3bb3196e505
- F src/btree.h 74d64b8f28cfa4a894d14d4ed64fa432cd697b98b61708d4351482ae15913e22
- F src/btreeInt.h 8ce1332edd89dfd2461d561ac10a0ab5601c8e06200cb5230596c3caaf54482e
- F src/build.c e8e776b52bc145cbf4e9fb88b99830083880fc2b174c2f96518fff15cbc72396
- F src/callback.c 4c19af69835787bfe790ac560f3071a824eb629f34e41f97b52ce5235c77de1c
+ F src/btmutex.c 6ffb0a22c19e2f9110be0964d0731d2ef1c67b5f7fabfbaeb7b9dabc4b7740ca
+ F src/btree.c ecaaf8d57cd8b5f4e3167bd59cf61cef031b4b2ee606e6afa11b96a60a14f9ef
+ F src/btree.h aa354b9bad4120af71e214666b35132712b8f2ec11869cb2315c52c81fad45cc
+ F src/btreeInt.h a9ae91868acc4b3146d47ae2a072aac2cf41ecb7386015752160c8e1a212d9f2
+ F src/build.c 7a7217f75f202eff03617ca447bb9c3bc07d5af49da1d3cff2b1a88e8e394686
+ F src/callback.c 4cd7225b26a97f7de5fee5ae10464bed5a78f2adefe19534cc2095b3a8ca484a
  F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
- F src/ctime.c 026dbdcdbd8c3cde98a88483ee88310ff43150ab164ad768f12cc700a11495ad
- F src/date.c 15082566229d4b1e5f24fdb490bf9bcc68824b911d70e3573ef075a1b9e2d26f
- F src/dbpage.c 90661a87e1db8bfbc8d2ebbdcd3749651ddb287c555c07a28fb17c7c591ffb68
- F src/dbstat.c 861e08690fcb0f2ee1165eff0060ea8d4f3e2ea10f80dab7d32ad70443a6ff2d
- F src/delete.c a8e844af211a48b13b5b358be77a12c860c6a557c21990ad51a548e2536500ce
- F src/expr.c d8c520edd0edf0b9c0ca467aa44c864ff17f265fd1cebbd224b6d5c625b638c3
+ F src/ctime.c 20507cc0b0a6c19cd882fcd0eaeda32ae6a4229fb4b024cfdf3183043d9b703d
+ F src/date.c aca9e0c08b400b21238b609aea7c09585396cd770985cf8f475560f69222dad3
+ F src/dbpage.c f3eea5f7ec47e09ee7da40f42b25092ecbe961fc59566b8e5f705f34335b2387
+ F src/dbstat.c ec92074baa61d883de58c945162d9e666c13cd7cf3a23bc38b4d1c4d0b2c2bef
+ F src/delete.c a9c6d3f51c0a31e9b831e0a0580a98d702904b42d216fee530940e40dec34873
+ F src/expr.c 871cfd80c516ee39d90414b2d3da2b5bc9c9e21fe87b7eb787ea7ae4b6461758
  F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
- F src/fkey.c d965ede15d8360c09ed59348940649ee647b192e784466837d7aefa836d1d91e
- F src/func.c a3407a6fbb0d4088d8d502e46f0ace63e0aeae7467ae23a9ca9815bbf9239761
- F src/global.c e83ee571b79ee3adc32e380cf554cf1254bc43763d23786c71721fbcdfbbb965
- F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19
+ F src/fkey.c 03c134cc8bffe54835f742ddea0b72ebfc8f6b32773d175c71b8afeea6cb5c83
+ F src/func.c 03e6b501f3056d0ba398bda17df938b2b566aa0b3ca7e1942a3cd1925d04ec36
+ F src/global.c bd0892ade7289f6e20bff44c07d06371f2ff9b53cea359e7854b9b72f65adc30
+ F src/hash.c c6af5f96a7a76d000f07c5402c48c318c2566beecdee9e78b9d9f60ce7119565
  F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51
- F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144
+ F src/hwtime.h b638809e083b601b618df877b2e89cb87c2a47a01f4def10be4c4ebb54664ac7
  F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
- F src/insert.c 173845e5a6bac96ae937409e4f876b631f26b31dabb9df8fd0eb3b130b2bb3a7
- F src/json.c 7749b98c62f691697c7ee536b570c744c0583cab4a89200fdd0fc2aa8cc8cbd6
+ F src/insert.c a8de1db43335fc4946370a7a7e47d89975ad678ddb15078a150e993ba2fb37d4
+ F src/json.c 39b1c7527f3111923e65f168a87b03b591f12a41400a63d05c119794bee36620
  F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
- F src/loadext.c 2ecb1441f9b1c22e9e022ee0776e67d259facf34b56ba892b206f0a294ee6f8c
- F src/main.c 135858d2ede0b83d779e71b07ede9c1d6b6eaab7b77bc2a85729584152769faf
- F src/malloc.c a9127efdcef92d6934c6339ea9813075b90edc0ce2e5c723556381a3828fb720
+ F src/loadext.c be5af440f3192c58681b5d43167dbca3ccbfce394d89faa22378a14264781136
+ F src/main.c 035be2e9ba2a0fc1701a8ab1880af3001a968a24556433538a6c073558ee4341
+ F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23
  F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
  F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de
  F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75
@@@ -536,45 -614,43 +617,45 @@@ F src/mutex_noop.c 9d4309c075ba9cc7249e
  F src/mutex_unix.c dd2b3f1cc1863079bc1349ac0fec395a500090c4fe4e11ab775310a49f2f956d
  F src/mutex_w32.c caa50e1c0258ac4443f52e00fe8aaea73b6d0728bd8856bedfff822cae418541
  F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6
- F src/os.c b1c4f2d485961e9a5b6b648c36687d25047c252222e9660b7cc25a6e1ea436ab
- F src/os.h 26890f540b475598cd9881dcc68931377b8d429d3ea3e2eeb64470cde64199f8
- F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
- F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586
- F src/os_unix.c 1f71ec8c87621f75c9c5ea973f5e8ce2f1d23fe760c01ed2814fe4b98b639825
- F src/os_win.c a8ea80037e81127ca01959daa87387cc135f325c88dc745376c4f760de852a10
 +F src/obj_interfaces.h c80525ea603357a79ddae45ba98eec2ac60a9092d1800811d92f3a0e0208d8a6
+ F src/os.c 81c9c1c52eab711e27e33fd51fe5788488d3a02bc1a71439857abbee5d0d2c97
+ F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63
+ F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06
+ F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a
+ F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107
+ F src/os_unix.c 1b3ddb7814c4bf37f494c04d2ab30c1ced5b2c927267e1930ce7cd388787a96d
+ F src/os_win.c 2b2411279f7b24f927591561303fc5871845732df42641cbf695c23640b16975
  F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
- F src/pager.c 42120492784fc9bcd9082b5c9b5e329b7318c357f9f3574a1bbfcf7418910356
+ F src/pager.c 45e2ef5e9eb5cc0138bcc32b5d2299479be80b206f621873f59149dfb517f496
  F src/pager.h f82e9844166e1585f5786837ddc7709966138ced17f568c16af7ccf946c2baa3
- F src/parse.y b86d56b446afb9c203d8354dc6c422818a62b4bbab52b76ab3da06d7b1d07e44
- F src/pcache.c 084e638432c610f95aea72b8509f0845d2791293f39d1b82f0c0a7e089c3bb6b
- F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586
- F src/pcache1.c 54881292a9a5db202b2c0ac541c5e3ef9a5e8c4f1c1383adb2601d5499a60e65
- F src/pragma.c d1aead03e8418ff586c7cfca344c50a914b8eb06abd841e8e91a982d823671da
+ F src/parse.y 146f9a1db7db5ef4299c6897d335e5abed348c2626190d2877d45ffa210fd4ca
+ F src/pcache.c 8ee13acccfd9accbf0af94910b7323dd7f7d55300d92ddafcf40e34fcc8e21be
+ F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5
+ F src/pcache1.c dee95e3cd2b61e6512dc814c5ab76d5eb36f0bfc9441dbb4260fccc0d12bbddc
+ F src/pragma.c 26ed2cfdc5c12aa1c707178635709684960288cacc9cff9d491a38ff10e395f1
  F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7
- F src/prepare.c fd940149c691684e7c1073c3787a7170e44852b02d1275d2e30a5b58e89cfcaf
- F src/printf.c 512574910a45341c8ad244bd3d4939968ebdfde215645b676fff01cc46e90757
- F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
- F src/resolve.c f72bb13359dd5a74d440df25f320dc2c1baff5cde4fc9f0d1bc3feba90b8932a
+ F src/prepare.c 6350675966bd0e7ac3a464af9dbfe26db6f0d4237f4e1f1acdb17b12ad371e6e
+ F src/printf.c b9320cdbeca0b336c3f139fd36dd121e4167dd62b35fbe9ccaa9bab44c0af38d
+ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
+ F src/resolve.c 3e53e02ce87c9582bd7e7d22f13f4094a271678d9dc72820fa257a2abb5e4032
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
- F src/select.c cc1a7581403fc074eee85283ba8d81de50a831ae175cb65a5751be00f621c0d5
- F src/shell.c.in e28bdaa7cbbc50936f06a592609ed6f7d1f04216a1d3f067516da243bd23fc46
+ F src/select.c f9333ef8181192c22662f5cb8d257efc4a2880f9ee4853c6c4616f783d27e1b5
 -F src/shell.c.in e0ca294a4ca6322fd0888bc090d34047347ff591ca9ac8846adc57b13ab91bee
++F src/shell.c.in 352557d6170fb0ab801aaa9d2ad6c3eb9e80bbb47fd49d286bc42379efba5ecb
 +F src/shext_linkage.h 27dcf7624df05b2a7a6d367834339a6db3636f3035157f641f7db2ec499f8f6d
- F src/sqlite.h.in 2a35f62185eb5e7ecc64a2f68442b538ce9be74f80f28a00abc24837edcf1c17
+ F src/sqlite.h.in 27ca1d4b2eda8feee468af5735182390e8fe4696522751eec0136d17323201ad
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
- F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e
- F src/sqliteInt.h 11cd60560cd14bb6eecd04d244faf4c1895417b97d89e2f50833f83c4611588d
+ F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4
+ F src/sqliteInt.h 91303fb4ee858b85ae1a8a48cc8f723339b81ba7138b42ee5c000083bfff0934
  F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
- F src/status.c 4a3da6d77eeb3531cb0dbdf7047772a2a1b99f98c69e90ce009c75fe6328b2c0
+ F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749
  F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
- F src/tclsqlite.c 1f6673991147bc2cecc08a40d22f9803b84c805b24b499fe727f392256f73474
- F src/test1.c 1356984e97bff07e4a8cc3863e892f05b3348678a74783bb6f350b76316736f1
- F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
+ F src/tclsqlite.c 8522a04fb9c84faa1d80354430ae0ee9349727a3a4b32e3cfe39b9be8324cabd
+ F src/test1.c 8eab61fb2813aa212d97ab188e85fc9ca7b89d9ff5ff05d59d9aa0c491a6c721
+ F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef
  F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
- F src/test4.c 7c4420e01c577b5c4add2cb03119743b1a357543d347773b9e717195ea967159
+ F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664
  F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d
  F src/test6.c ae73a3a42bbc982fb9e301b84d30bda65a307be48c6dff20aba1461e17a9b0ce
- F src/test7.c 5612e9aecf934d6df7bba6ce861fdf5ba5456010
  F src/test8.c 0c856d6ff6b0d2ff6696addc467a15ed17c6910f14475302cd5b3b4e54406161
  F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5
  F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a
@@@ -607,17 -683,14 +688,16 @@@ F src/test_quota.c 6cb9297115b551f433a9
  F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d
  F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b
  F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b
- F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe
 +F src/test_shellext_c.c aee192ee9ed63c1a6bde180a74ace1243a235f6fbd583aad67094c1ed6e1ec26
 +F src/test_shellext_cpp.cpp 2525913692646e06e9f68206abce155fbcfd92a0c069ebf3937f58f752b871ad
  F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a
  F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e
- F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939
- F src/test_tclsh.c c4065ced25126e25c40122c5ff62dc89902ea617d72cdd27765151cdd7fcc477
+ F src/test_syscall.c 9fdb13b1df05e639808d44fcb8f6064aaded32b6565c00b215cfd05a060d1aca
+ F src/test_tclsh.c 3ff5d188a72f00807425954ea3b493dfd3a4b890ecc6700ea83bad2fd1332ecf
  F src/test_tclvar.c 33ff42149494a39c5fbb0df3d25d6fafb2f668888e41c0688d07273dcb268dfc
- F src/test_thread.c 269ea9e1fa5828dba550eb26f619aa18aedbc29fd92f8a5f6b93521fbb74a61c
+ F src/test_thread.c 7ddcf0c8b79fa3c1d172f82f322302c963d923cdb503c6171f3c8081586d0b01
  F src/test_vdbecov.c f60c6f135ec42c0de013a1d5136777aa328a776d33277f92abac648930453d43
- F src/test_vfs.c 2cc38a79892017702d13da79ad5152c196eec19bbd67fbde4d88065aac894a84
+ F src/test_vfs.c 193c18da3dbf62a0e33ae7a240bbef938a50846672ee947664512b77d853fe81
  F src/test_vfstrace.c bab9594adc976cbe696ff3970728830b4c5ed698
  F src/test_windirent.c a895e2c068a06644eef91a7f0a32182445a893b9a0f33d0cdb4283dca2486ac1
  F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a90484215
@@@ -1298,10 -1392,11 +1399,11 @@@ F test/pagesize.test 5769fc62d8c890a83a
  F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035ce4b3
  F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
  F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
+ F test/pendingrace.test cbdf0f74bc939fb43cebad64dda7a0b5a3941a10b7e9cc2b596ff3e423a18156
  F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
- F test/permutations.test 804df60ac3d1ac5ced27aa04b255648db63880412a267564db065ffd00147c32
 -F test/permutations.test 8bd6b6db541e2a7f9bb894be99ef5c00526b23762c4a00c574e1cba697495125
++F test/permutations.test aeebc29c7b225487c0235fc8be65f51e2cc19c7ff00c4c0c342315ca6957c0d1
  F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f
- F test/pragma.test cae534c12a033a5c319ccc94f50b32811acdef9f67bf19a82ff42697caccd69f
+ F test/pragma.test 57a36226218c03cfb381019fe43234b2cefbd8a1f12825514f906a17ccf7991e
  F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f
  F test/pragma3.test 92a46bbea12322dd94a404f49edcfbfc913a2c98115f0d030a7459bb4712ef31
  F test/pragma4.test ca5e4dfc46adfe490f75d73734f70349d95a199e6510973899e502eef2c8b1f8
@@@ -1323,14 -1419,14 +1426,14 @@@ F test/randexpr1.tcl 40dec52119ed3a2b8b
  F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df
  F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736
  F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8
- F test/recover.test ccb8c2623902a92ebb76770edd075cb4f75a4760bb7afde38026572c6e79070d
- F test/regexp1.test 0c3ff80f66b0eff80e623eb5db7a3dad512095c573d78ac23009785f6d8f51ce
+ F test/recover.test fd5199f928757cb308661b5fdca1abc19398a798ff7f24b57c3071e9f8e0471e
+ F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1
  F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5
  F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d
- F test/releasetest_data.tcl 07c02f23706c17a8d1c00eb17f7b98aaee50cc3077f3a52d5d71c13859fb8ef7
- F test/resetdb.test 8062cf10a09d8c048f8de7711e94571c38b38168db0e5877ba7561789e5eeb2b
 -F test/releasetest_data.tcl b550dd1b122a9c969df794d05ea272df535f10ff1a245062e7ba080822378016
++F test/releasetest_data.tcl 59400d9e7acbfebdabd87ae89a2b979867af3ce8f2a95c0fb801e8a4781f1f4a
+ F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3
  F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb
- F test/returning1.test c43b8370a351f77aec6d71f4a2cde59b849369ed1933261a2c2c69e23e34ff5e
+ F test/returning1.test db532cde29d6aebbc48c6ddc3149b30476f8e69ca7a2c4b53986c7635e6fd8ec
  F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4
  F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa
  F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f
@@@ -1397,21 -1495,19 +1502,21 @@@ F test/shared4.test c75f476804e76e26bf6
  F test/shared6.test 866bb4982c45ce216c61ded5e8fde4e7e2f3ffa9
  F test/shared7.test a81e99f83e6c51b02ac99c96fb3a2a7b5978c956
  F test/shared8.test 933ed7d71f598bb6c7a8c192a3cd30f2562fdccf514df383798599c34ffa672f
- F test/shared9.test 5f2a8f79b4d6c7d107a01ffa1ed05ae7e6333e21
- F test/sharedA.test 49d87ec54ab640fbbc3786ee3c01de94aaa482a3a9f834ad3fe92770eb69e281
- F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e
+ F test/shared9.test 600a257fe9d8b0272746b230e761aa1bd8802ca4cf3ba5b2136b9204f3d51efa
+ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e212700
+ F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707
  F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
  F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
- F test/shell1.test 87fed4b2fb33d3aa4d64552be8be8ec41b383f3f57986ff80e1a2b14370a5c5c
- F test/shell2.test fc6bb55f5ceaaffa284cb994aa00fd56f7ead09949c9db01c3846d65a76a7748
- F test/shell3.test e12fb76031b1d6dec6e778b098ab8720987cb447120abe0dd234fbae1fc05c2c
- F test/shell4.test b232688061cce531f42ec067f3b5760e31d12409e566e2ae230951036dd156f1
- F test/shell5.test 39d2cffb3c1c67456b2c42eb5e46bec1e3780c68a6d71bb156e012d3f53733c5
 -F test/shell1.test 300b77328aaafb9f3e7a53a26e4162fbf92181d92251d259ff105a2275ff998d
 -F test/shell2.test 09a202f57e7cd99788537f763e0845796a173fcea06a0d199a08d69446fe1daf
 -F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a
 -F test/shell4.test 9abd0c12a7e20a4c49e84d5be208d2124fa6c09e728f56f1f4bee0f02853935f
 -F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd1473b
++F test/shell1.test 62896b0179d402468b54855209cb730e9f15df4896f9ef168113e69b7eec97bf
++F test/shell2.test 814a98e93b480162c6fd4fed573cff826225ebd1ed7f8b3e36a6fab0c0c303d1
++F test/shell3.test 9dd38fe5d98047dca5e71a09e64272b3fb75d8131bfb9137122785ed2c8c3802
++F test/shell4.test 8116d7b9dbefe6e2237908afbd6738637e0426486373231d3ad8984471d4e04c
++F test/shell5.test ec82248dda87329f7dfced8637515b68db4ee75d94c8129dc017376fb61a8247
  F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
  F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
- F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae
+ F test/shell8.test 3fd093d481aaa94dc77fb73f1044c1f19c7efe3477a395cc4f7450133bc54915
 +F test/shell_x/shell10.test 43c0b10cb4998c91b18861efef9466f6170e43c17a76e28600356a1cb6b41cf4
 +F test/shell_x/shell9.test 867be89a86bc0c2fcf73e4759c85fd38d7e15fef71238e725ba4b6ca6897a0af
  F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5
  F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
  F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
@@@ -1486,12 -1582,14 +1591,14 @@@ F test/tempdb.test 4cdaa23ddd8acb4d79cb
  F test/tempdb2.test 353864e96fd3ae2f70773d0ffbf8b1fe48589b02c2ec05013b540879410c3440
  F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900
  F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
- F test/temptable2.test d2940417496e2b9548e01d09990763fbe88c316504033256d51493e1f1a5ce6a
+ F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d1631311a16
  F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
  F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
- F test/tester.tcl 25a215da2f02bd1bec6836cfa8154e84b89ccc13b3496d4f0beebe31e56ad8b7
- F test/thread001.test b61a29dd87cf669f5f6ac96124a7c97d71b0c80d9012746072055877055cf9ef
- F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58
 -F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede
++F test/tester.tcl 176a2ea601e01ad15450fbbb88b28ae66efbf94e684f52307741b26206f1cefb
+ F test/testrunner.tcl 59490f189cac99b16b0376d0cc0a7ecfb753a84b89c9f4c361af337d88db53ac
+ F test/testrunner_data.tcl 8169c68654ac8906833b8a6aadca973358a441ebf88270dd05c153e5f96f76b8
+ F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899
+ F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502
  F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
  F test/thread004.test f51dfc3936184aaf73ee85f315224baad272a87f
  F test/thread005.test 50d10b5684399676174bd96c94ad4250b1a2c8b6
@@@ -1777,14 -1880,14 +1889,14 @@@ F test/walprotocol2.test 7d3b6b4bf0b12f
  F test/walro.test cb438d05ba0d191f10b688e39c4f0cd5b71569a1d1f4440e5bdf3c6880e08c20
  F test/walro2.test 33955a6fd874dd9724005e17f77fef89d334b3171454a1256fe4941a96766cdc
  F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68
- F test/walsetlk.test 3185bebc90557e0d611442c8d64f7a0cb7b06f8e156eea37a4a7358f722715be
- F test/walshared.test 0befc811dcf0b287efae21612304d15576e35417
+ F test/walsetlk.test 34c901443b31ab720afc463f5b236c86ca5c4134402573dce91aa0761de8db5a
+ F test/walshared.test 42e3808582504878af237ea02c42ca793e8a0efaa19df7df26ac573370dbc7a3
  F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f
  F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2af51747
- F test/walvfs.test bccb3e0d235ef85e276f491d34db32c9ada1ea67be8d9f10aabe7b30319ec656
+ F test/walvfs.test e1a6ad0f3c78e98b55c3d5f0889cf366cc0d0a1cb2bccb44ac9ec67384adc4a1
  F test/wapp.tcl b440cd8cf57953d3a49e7ee81e6a18f18efdaf113b69f7d8482b0710a64566ec
- F test/wapptest.tcl c55a4669d02e982921f1dc37ababa21eb14123ec73a396692165e927c16ff061 x
- F test/where.test 8c6bbd0cae8feae142a7946e3484a802fa566bacf38452b1c3e48cb77321f9a4
 -F test/wapptest.tcl 1bea58a6a8e68a73f542ee4fca28b771b84ed803bd0c9e385087070b3d747b3c x
++F test/wapptest.tcl 15ef56ed36b6b03f3ffeb7e1692ec4286bf909c0daa9bbe6215daa9f324fbf3a x
+ F test/where.test 59abb854eee24f166b5f7ba9d17eb250abc59ce0a66c48912ffb10763648196d
  F test/where2.test 03c21a11e7b90e2845fc3c8b4002fc44cc2797fa74c86ee47d70bd7ea4f29ed6
  F test/where3.test 5b4ffc0ac2ea0fe92f02b1244b7531522fe4d7bccf6fa8741d54e82c10e67753
  F test/where4.test 4a371bfcc607f41d233701bdec33ac2972908ba8
@@@ -1887,14 -1993,14 +2002,14 @@@ F tool/max-limits.c cbb635fbb37ae4d05f2
  F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176
  F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8
  F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x
- F tool/mkctimec.tcl ac96a74f5e6d9dac672d5229f79c583d3357a50e7d098e473e6b2ce2f8ae1704 x
- F tool/mkkeywordhash.c 35bfc41adacc4aa6ef6fca7fd0c63e0ec0534b78daf4d0cfdebe398216bbffc3
+ F tool/mkctimec.tcl 38e3db33210a200aae791635125052a643a27aa0619a0debf19aa9c55e1b2dde x
+ F tool/mkkeywordhash.c 9822bd1f58a70e5f84179df3045bba4791df69180e991bec88662703f2e93761
  F tool/mkmsvcmin.tcl 6ecab9fe22c2c8de4d82d4c46797bda3d2deac8e763885f5a38d0c44a895ab33
  F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef
- F tool/mkopcodeh.tcl 5dab48c49a25452257494e9601702ab63adaba6bd54a9b382615fa52661c8f8c
+ F tool/mkopcodeh.tcl 769d9e6a8b462323150dc13a8539d6064664b72974f7894befe2491cc73e05cd
  F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
  F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d
 -F tool/mkshellc.tcl b7adf08b82de60811d2cb6af05ff59fc17e5cd6f3e98743c14eaaa3f8971fed0
 +F tool/mkshellc.tcl db5df976cdb94518b99551df7af737edaf92f0bc276739b656154980c5054277 x
  F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
  F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
  F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f
@@@ -1960,8 -2068,8 +2077,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
  F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
  F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P 9b37e0be1a416a49671bbfb1a7e62c66f07b3a9ef6b4ce6cf72a135b046675c5
- R a90d7f8c6d6e0ed2a501682f3b3291b1
 -P 38544b11f0e19cc6c6f8230a89d28e36c7c3587481deaac6cedbf82338ca0d47
 -R 5e5d8ddbeb8cc3782b51107bf84b5cec
 -U dan
 -Z 798dd3ac32d38e6a1c50db3cf54d26ed
++P 3db119c8d754979ceb16253f1b79b645a5bc68b399406cacc4c50a2a71e84e2d 4ffae48e831eedf8f5e61dc6d38817c0fdccfb2f4f1189d07f9722b9e3a48b5e
++R 71234b6949650e0e0a7eecae7256efe6
 +U larrybr
- Z a4435e17e1a04d73b0b697a3cb1915ae
++Z 488bdccb3a6edb132ee652b96b26bff4
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index d00932098a841c13a86901f213a498097e1e662e,16a0d0a53a531461baacb8b3fde2730354a69191..150331299d9a7d9cb4b4a5080e79c3a4116945a5
@@@ -1,1 -1,1 +1,1 @@@
- 3db119c8d754979ceb16253f1b79b645a5bc68b399406cacc4c50a2a71e84e2d
 -4ffae48e831eedf8f5e61dc6d38817c0fdccfb2f4f1189d07f9722b9e3a48b5e
++00d0d5b10556bcc4ffb8d6083669134a913bc6ae7fcc787af5b1c292923354e8
diff --cc src/shell.c.in
index 1e1ca18382795b9ed61a97969aac485121213382,567752e9b0f19dbbd84b6716cd5251b6a3e1a1c5..bcdbdc3e0b6e683f746e8fd144b92908bc1e1402
@@@ -488,22 -487,28 +512,34 @@@ static volatile int seenInterrupt = 0
  */
  static char *Argv0;
  
 -/*
 -** Prompt strings. Initialized in main. Settable with
 -**   .prompt main continue
 -*/
 -#define PROMPT_LEN_MAX 20
 -/* First line prompt.   default: "sqlite> " */
 -static char mainPrompt[PROMPT_LEN_MAX];
 -/* Continuation prompt. default: "   ...> " */
 -static char continuePrompt[PROMPT_LEN_MAX];
 -
+ /* This is variant of the standard-library strncpy() routine with the
+ ** one change that the destination string is always zero-terminated, even
+ ** if there is no zero-terminator in the first n-1 characters of the source
+ ** string.
+ */
+ static char *shell_strncpy(char *dest, const char *src, size_t n){
+   size_t i;
+   for(i=0; i<n-1 && src[i]!=0; i++) dest[i] = src[i];
+   dest[i] = 0;
+   return dest;
+ }
 +/*
 +** Prompt strings. Initialized in main. Settable with
 +**   .prompt main continue
 +*/
 +#define PROMPT_LEN_MAX 20
 +
 +/* First line prompt.   default: "sqlite> " */
 +static char mainPrompt[PROMPT_LEN_MAX];
 +/* Continuation prompt. default: "   ...> " */
 +static char continuePrompt[PROMPT_LEN_MAX];
- #define SET_MAIN_PROMPT(z) strncpy(mainPrompt,z,PROMPT_LEN_MAX-1)
- #define SET_MORE_PROMPT(z) strncpy(continuePrompt,z,PROMPT_LEN_MAX-1);
++#define SET_MAIN_PROMPT(z) shell_strncpy(mainPrompt,z,PROMPT_LEN_MAX-1)
++#define SET_MORE_PROMPT(z) shell_strncpy(continuePrompt,z,PROMPT_LEN_MAX-1);
 +/* Prompts as ready to be used by shell's input function */
 +static Prompts shellPrompts = { mainPrompt, continuePrompt, 0 };
 +
 +#ifdef SQLITE_OMIT_DYNAPROMPT
  /*
  ** Optionally disable dynamic continuation prompt.
  ** Unless disabled, the continuation prompt shows open SQL lexemes if any,
@@@ -561,44 -565,175 +597,175 @@@ static void setLexemeOpen(struct DynaPr
  }
  
  /* Upon demand, derive the continuation prompt to display. */
 -static char *dynamicContinuePrompt(void){
 +static void dynamicContinuePrompt(void){
    if( continuePrompt[0]==0
        || (dynPrompt.zScannerAwaits==0 && dynPrompt.inParenLevel == 0) ){
 -    return continuePrompt;
 -  }else{
 -    if( dynPrompt.zScannerAwaits ){
 -      size_t ncp = strlen(continuePrompt);
 -      size_t ndp = strlen(dynPrompt.zScannerAwaits);
 -      if( ndp > ncp-3 ) return continuePrompt;
 +  plain_continuation:
 +    shellPrompts.zContinue = continuePrompt;
 +    return;
 +  }
 +  if( dynPrompt.zScannerAwaits ){
 +    size_t ncp = strlen(continuePrompt);
 +    size_t ndp = strlen(dynPrompt.zScannerAwaits);
 +    if( ndp > ncp-3 ) goto plain continuation;
        strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits);
        while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' ';
-       strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,
-             PROMPT_LEN_MAX-4);
+       shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,
 -              PROMPT_LEN_MAX-4);
++                    PROMPT_LEN_MAX-4);
 +  }else{
 +    if( dynPrompt.inParenLevel>9 ){
-       strncpy(dynPrompt.dynamicPrompt, "(..", 4);
++      shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4);
 +    }else if( dynPrompt.inParenLevel<0 ){
-       strncpy(dynPrompt.dynamicPrompt, ")x!", 4);
++      shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4);
      }else{
-       strncpy(dynPrompt.dynamicPrompt, "(x.", 4);
 -      if( dynPrompt.inParenLevel>9 ){
 -        shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4);
 -      }else if( dynPrompt.inParenLevel<0 ){
 -        shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4);
 -      }else{
 -        shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4);
 -        dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel);
 -      }
 -      shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, PROMPT_LEN_MAX-4);
++      shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4);
 +      dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel);
      }
-     strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, PROMPT_LEN_MAX-4);
++    shell_strncpy(dynPrompt.dynamicPrompt+3,continuePrompt+3,PROMPT_LEN_MAX-4);
    }
 -  return dynPrompt.dynamicPrompt;
 +  shellPrompts.zContinue = dynPrompt.dynamicPrompt;
  }
  #endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */
  
+ #if SHELL_WIN_UTF8_OPT
+ /* Following struct is used for -utf8 operation. */
+ static struct ConsoleState {
+   int stdinEof;      /* EOF has been seen on console input */
+   int infsMode;      /* Input file stream mode upon shell start */
+   UINT inCodePage;   /* Input code page upon shell start */
+   UINT outCodePage;  /* Output code page upon shell start */
+   HANDLE hConsoleIn; /* Console input handle */
+   DWORD consoleMode; /* Console mode upon shell start */
+ } conState = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 };
+ /*
+ ** Prepare console, (if known to be a WIN32 console), for UTF-8
+ ** input (from either typing or suitable paste operations) and for
+ ** UTF-8 rendering. This may "fail" with a message to stderr, where
+ ** the preparation is not done and common "code page" issues occur.
+ */
+ static void console_prepare(void){
+   HANDLE hCI = GetStdHandle(STD_INPUT_HANDLE);
+   DWORD consoleMode = 0;
+   if( isatty(0) && GetFileType(hCI)==FILE_TYPE_CHAR
+       && GetConsoleMode( hCI, &consoleMode) ){
+     if( !IsValidCodePage(CP_UTF8) ){
+       fprintf(stderr, "Cannot use UTF-8 code page.\n");
+       console_utf8 = 0;
+       return;
+     }
+     conState.hConsoleIn = hCI;
+     conState.consoleMode = consoleMode;
+     conState.inCodePage = GetConsoleCP();
+     conState.outCodePage = GetConsoleOutputCP();
+     SetConsoleCP(CP_UTF8);
+     SetConsoleOutputCP(CP_UTF8);
+     consoleMode |= ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
+     SetConsoleMode(conState.hConsoleIn, consoleMode);
+     conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT);
+     console_utf8 = 1;
+   }else{
+     console_utf8 = 0;
+   }
+ }
+ /*
+ ** Undo the effects of console_prepare(), if any.
+ */
+ static void SQLITE_CDECL console_restore(void){
+   if( console_utf8 && conState.inCodePage!=0
+       && conState.hConsoleIn!=INVALID_HANDLE_VALUE ){
+     _setmode(_fileno(stdin), conState.infsMode);
+     SetConsoleCP(conState.inCodePage);
+     SetConsoleOutputCP(conState.outCodePage);
+     SetConsoleMode(conState.hConsoleIn, conState.consoleMode);
+     /* Avoid multiple calls. */
+     conState.hConsoleIn = INVALID_HANDLE_VALUE;
+     conState.consoleMode = 0;
+     console_utf8 = 0;
+   }
+ }
+ /*
+ ** Collect input like fgets(...) with special provisions for input
+ ** from the Windows console to get around its strange coding issues.
+ ** Defers to plain fgets() when input is not interactive or when the
+ ** startup option, -utf8, has not been provided or taken effect.
+ */
+ static char* utf8_fgets(char *buf, int ncmax, FILE *fin){
+   if( fin==0 ) fin = stdin;
+   if( fin==stdin && stdin_is_interactive && console_utf8 ){
+ # define SQLITE_IALIM 150
+     wchar_t wbuf[SQLITE_IALIM];
+     int lend = 0;
+     int noc = 0;
+     if( ncmax==0 || conState.stdinEof ) return 0;
+     buf[0] = 0;
+     while( noc<ncmax-7-1 && !lend ){
+       /* There is room for at least 2 more characters and a 0-terminator. */
+       int na = (ncmax > SQLITE_IALIM*4+1 + noc)
+         ? SQLITE_IALIM : (ncmax-1 - noc)/4;
+ # undef SQLITE_IALIM
+       DWORD nbr = 0;
+       BOOL bRC = ReadConsoleW(conState.hConsoleIn, wbuf, na, &nbr, 0);
+       if( !bRC || (noc==0 && nbr==0) ) return 0;
+       if( nbr > 0 ){
+         int nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR,
+                                       wbuf,nbr,0,0,0,0);
+         if( nmb !=0 && noc+nmb <= ncmax ){
+           int iseg = noc;
+           nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR,
+                                     wbuf,nbr,buf+noc,nmb,0,0);
+           noc += nmb;
+           /* Fixup line-ends as coded by Windows for CR (or "Enter".)*/
+           if( noc > 0 ){
+             if( buf[noc-1]=='\n' ){
+               lend = 1;
+               if( noc > 1 && buf[noc-2]=='\r' ){
+                 buf[noc-2] = '\n';
+                 --noc;
+               }
+             }
+           }
+           /* Check for ^Z (anywhere in line) too. */
+           while( iseg < noc ){
+             if( buf[iseg]==0x1a ){
+               conState.stdinEof = 1;
+               noc = iseg; /* Chop ^Z and anything following. */
+               break;
+             }
+             ++iseg;
+           }
+         }else break; /* Drop apparent garbage in. (Could assert.) */
+       }else break;
+     }
+     /* If got nothing, (after ^Z chop), must be at end-of-file. */
+     if( noc == 0 ) return 0;
+     buf[noc] = 0;
+     return buf;
+   }else{
+     return fgets(buf, ncmax, fin);
+   }
+ }
+ # define fgets(b,n,f) utf8_fgets(b,n,f)
+ #endif /* SHELL_WIN_UTF8_OPT */
++
  /*
  ** Render output like fprintf().  Except, if the output is going to the
- ** console and if this is running on a Windows machine, translate the
- ** output from UTF-8 into MBCS.
+ ** console and if this is running on a Windows machine, and if the -utf8
+ ** option is unavailable or (available and inactive), translate the
+ ** output from UTF-8 into MBCS for output through 8-bit stdout stream.
+ ** (With -utf8 active, no translation is needed and must not be done.)
  */
  #if defined(_WIN32) || defined(WIN32)
 -void utf8_printf(FILE *out, const char *zFormat, ...){
 -  va_list ap;
 -  va_start(ap, zFormat);
 -  if( stdout_is_console && (out==stdout || out==stderr)
 +void vf_utf8_printf(FILE *out, const char *zFormat, va_list ap){
-   if( stdout_is_console && (out==STD_OUT || out==STD_ERR) ){
++  if( stdout_is_console && (out==STD_OUT || out==STD_ERR)
+ # if SHELL_WIN_UTF8_OPT
+       && !console_utf8
+ # endif
 -  ){
++      ){
      char *z1 = sqlite3_vmprintf(zFormat, ap);
      char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0);
      sqlite3_free(z1);
@@@ -646,16 -765,9 +813,16 @@@ static void shell_out_of_memory(void)
  /* Check a pointer to see if it is NULL.  If it is NULL, exit with an
  ** out-of-memory error.
  */
- static void shell_check_oom(void *p){
+ static void shell_check_oom(const void *p){
    if( p==0 ) shell_out_of_memory();
  }
 +/* This pattern is ubiquitous and subject to change, so encapsulate it. */
 +#define SHELL_ASSIGN_OOM_CHECK(lv, pv) lv = pv, shell_check_oom(lv)
 +
 +static void shell_newstr_assign(char **pLV, char *z){
 +  *pLV = z;
 +  if( *pLV==0 ) shell_out_of_memory();
 +}
  
  /*
  ** Write I/O traces to the following stream.
@@@ -894,9 -944,14 +1061,14 @@@ static char *local_getline(char *zLine
      }
    }
  #if defined(_WIN32) || defined(WIN32)
-   /* For interactive input on Windows systems, translate the
-   ** multi-byte characterset characters into UTF-8. */
-   if( stdin_is_interactive && pInSrc==&stdInSource ){
+   /* For interactive input on Windows systems, without -utf8,
+   ** translate the multi-byte characterset characters into UTF-8.
+   ** This is the translation that predates the -utf8 option. */
 -  if( stdin_is_interactive && in==stdin
++  if( stdin_is_interactive && pInSrc==&stdInSource
+ # if SHELL_WIN_UTF8_OPT
+       && !console_utf8
+ # endif /* SHELL_WIN_UTF8_OPT */
 -  ){
++      ){
      char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0);
      if( zTrans ){
        i64 nTrans = strlen(zTrans)+1;
  ** routine that can be reused.
  **
  ** The result is stored in space obtained from malloc() and must either
 -** be freed by the caller or else passed back into this routine via the
 -** zPrior argument for reuse.
 +** be freed by the caller, using the same allocator[a], or else passed
 +** back into this routine via the zPrior argument for reuse.
 +**
 +** [a. This function is exposed for use by shell extensions which may
 +**   have no access to "the same allocator". This is why the function
 +**   following this one exists, also exposed to shell extensions. ]
 +**
 +** If this function is called until it returns NULL, and the prior return
 +** has been passed in for resuse, then the caller need/must not free it.
 +** Otherwise, (in case of an early termination of reading from the given
 +** input), the caller is responsible for freeing a prior, non-NULL return.
 +**
 +** The trailing newline (or its ilk), if any, is trimmed.
 +** The input line number is adjusted (via delegation or directly.)
  */
 -#ifndef SQLITE_SHELL_FIDDLE
 -static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
 -  char *zPrompt;
 -  char *zResult;
 -  if( in!=0 ){
 -    zResult = local_getline(zPrior, in);
 +static char *one_input_line(InSource *pInSrc, char *zPrior,
 +                            int isContinuation, Prompts *pCue){
 +  if( !INSOURCE_IS_INTERACTIVE(pInSrc) ){
 +    return local_getline(zPrior, pInSrc);
    }else{
 -    zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt;
 +    static Prompts cueDefault = { "$ ","> ", 0 };
 +    const char *zPrompt;
++    char *zResult;
 +    if( pCue==0 ) pCue = &cueDefault;
 +    zPrompt = isContinuation ? pCue->zContinue : pCue->zMain;
  #if SHELL_USE_LOCAL_GETLINE
      printf("%s", zPrompt);
      fflush(stdout);
-     return local_getline(zPrior, pInSrc);
+     do{
 -      zResult = local_getline(zPrior, stdin);
++      zResult = local_getline(zPrior, pInSrc);
+       zPrior = 0;
+       /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */
+       if( zResult==0 ) sqlite3_sleep(50);
+     }while( zResult==0 && seenInterrupt>0 );
  #else
-     char *zResult;
      free(zPrior);
      zResult = shell_readline(zPrompt);
-     ++pInSrc->lineno;
+     while( zResult==0 ){
+       /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */
+       sqlite3_sleep(50);
+       if( seenInterrupt==0 ) break;
+       zResult = shell_readline("");
+     }
      if( zResult && *zResult ) shell_add_history(zResult);
-     return zResult;
++    ++pInSrc->lineno;
  #endif
- ** Alternate one_input_line() for wasm mode. This is not in the primary impl
- ** because we need the global shellState and cannot access it from that function
- ** without moving lots of code around (creating a larger/messier diff).
++    return zResult;
 +  }
 +}
 +#else /* !defined(SQLITE_SHELL_FIDDLE) */
 +/*
++** Alternate one_input_line() impl for wasm mode. This is not in the primary
++** impl because we need the global shellState and cannot access it from that
++** function without moving lots of code around (creating a larger/messier diff).
 +*/
 +static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
 +  /* Parse the next line from shellState.wasm.zInput. */
 +  const char *zBegin = shellState.wasm.zPos;
 +  const char *z = zBegin;
 +  char *zLine = 0;
 +  i64 nZ = 0;
 +
 +  UNUSED_PARAMETER(in);
 +  UNUSED_PARAMETER(isContinuation);
 +  if(!z || !*z){
 +    return 0;
    }
 -  return zResult;
 +  while(*z && isspace(*z)) ++z;
 +  zBegin = z;
 +  for(; *z && '\n'!=*z; ++nZ, ++z){}
 +  if(nZ>0 && '\r'==zBegin[nZ-1]){
 +    --nZ;
 +  }
 +  shellState.wasm.zPos = z;
 +  zLine = realloc(zPrior, nZ+1);
 +  shell_check_oom(zLine);
 +  memcpy(zLine, zBegin, nZ);
 +  zLine[nZ] = 0;
 +  return zLine;
 +}
 +#endif /* SQLITE_SHELL_FIDDLE */
 +
 +/* For use by shell extensions. See footnote [a] to above function. */
 +void free_input_line(char *z){
 +  free(z);
  }
 -#endif /* !SQLITE_SHELL_FIDDLE */
  
  /*
  ** Return the value of a hexadecimal digit.  Return -1 if the input
@@@ -1294,13 -1312,17 +1478,20 @@@ INCLUDE test_windirent.
  INCLUDE test_windirent.c
  #define dirent DIRENT
  #endif
 -INCLUDE ../ext/misc/memtrace.c
  INCLUDE ../ext/misc/shathree.c
 +INCLUDE ../ext/misc/fileio.c
 +INCLUDE ../ext/misc/completion.c
 +INCLUDE ../ext/misc/appendvfs.c
 +INCLUDE ../ext/misc/memtrace.c
  INCLUDE ../ext/misc/uint.c
  INCLUDE ../ext/misc/decimal.c
+ #undef sqlite3_base_init
+ #define sqlite3_base_init sqlite3_base64_init
+ INCLUDE ../ext/misc/base64.c
+ #undef sqlite3_base_init
+ #define sqlite3_base_init sqlite3_base85_init
+ #define OMIT_BASE85_CHECKER
+ INCLUDE ../ext/misc/base85.c
  INCLUDE ../ext/misc/ieee754.c
  INCLUDE ../ext/misc/series.c
  INCLUDE ../ext/misc/regexp.c
@@@ -1603,70 -1478,14 +1792,70 @@@ typedef struct ShellInState 
      const char * zDefaultDbName; /* Default name for db file */
    } wasm;
  #endif
 -};
 +
 +#if SHELL_DYNAMIC_EXTENSION
 +  /* extension management */
 +  int numExtLoaded;      /* Number of extensions presently loaded or emulated */
 +  ShExtInfo *pShxLoaded; /* Tracking and use info for loaded shell extensions */
 +  int ixExtPending;      /* Index of pending extension load operations if !0  */
 +  /* scripting integration */
 +  ScriptSupport *script; /* Scripting support, if any, from loaded extension */
 +  ExtensionId scriptXid; /* Id of extension which is supporting scripting */
 +  /* shell event subscription list */
 +  int numSubscriptions;  /* Number of active entries in below list */
 +  struct EventSubscription {
 +    ExtensionId eid;
 +    void *pvUserData;
 +    ShellEventNotify eventHandler;
 +  } *pSubscriptions;     /* The current shell event subscriptions */
 +  u8 bDbDispatch;        /* Cache fact of dbShell dispatch table */
 +  DotCommand *pUnknown;  /* Last registered "unknown" dot command */
 +#endif
 +
 +#if SHELL_DATAIO_EXT
 +  ExportHandler *pFreeformExporter;  /* Default freeform mode exporter */
 +  ExportHandler *pColumnarExporter;  /* Default columnar mode exporter */
 +  ExportHandler *pActiveExporter;    /* Presently active exporter */
 +#endif
 +
 +  ShellExState *pSXS;    /* Pointer to companion, exposed shell state */
 +} ShellInState;
  
  #ifdef SQLITE_SHELL_FIDDLE
 -static ShellState shellState;
 +/* For WASM, keep a static instance so pseudo-main can be called repeatedly. */
 +static ShellInState shellState;
  #endif
  
-     /* Fall thru, suspension is in effect. */
 +/*
 +** Limit input nesting via .read or any other input redirect.
 +** It's not too expensive, so a generous allowance can be made.
 +*/
 +#define MAX_INPUT_NESTING 25
 +
 +/*
 +** This procedure updates the bSafeMode flag after completion of any
 +** operation (dot-command, SQL submission, or script execution) that
 +** counts as one for which safe mode might be suspended.
 +** bSafeModeFuture has 3 states salient here:
 +** equal 0 => Safe mode is and will remain inactive.
 +** equal 1 => Safe mode is and will remain active.
 +** N > 1 => Safe mode is suspended for N-1 operations.
 +*/
 +static void updateSafeMode(ShellInState *psi){
 +  switch( psi->bSafeModeFuture ){
 +  case 2:
 +  default:
 +    --psi->bSafeModeFuture;
++    deliberate_fall_through;
 +  case 0:
 +    psi->bSafeMode = 0;
 +    break;
 +  case 1:
 +    psi->bSafeMode = 1;
 +  }
 +}
  
 -/* Allowed values for ShellState.autoEQP
 +/* Allowed values for ShellInState.autoEQP
  */
  #define AUTOEQP_off      0           /* Automatic EXPLAIN QUERY PLAN is off */
  #define AUTOEQP_on       1           /* Automatic EQP is on */
@@@ -2405,16 -2117,8 +2597,15 @@@ static void output_csv(ShellExState *ps
  */
  static void interrupt_handler(int NotUsed){
    UNUSED_PARAMETER(NotUsed);
-   seenInterrupt++;
-   if( seenInterrupt>2 ){
 -  if( ++seenInterrupt>1 ) exit(1);
 -  if( globalDb ) sqlite3_interrupt(globalDb);
++  if( ++seenInterrupt>1 ){
 +    sqlite3_mutex_free(pGlobalDbLock);
 +    exit(1);
 +  }
 +  if( globalDb ){
 +    sqlite3_mutex_enter(pGlobalDbLock);
 +    sqlite3_interrupt(globalDb);
 +    sqlite3_mutex_leave(pGlobalDbLock);
 +  }
  }
  
  #if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
@@@ -3084,9 -2686,9 +3276,9 @@@ static int shell_callback
            sqlite3_uint64 ur;
            memcpy(&ur,&r,sizeof(r));
            if( ur==0x7ff0000000000000LL ){
-             raw_printf(out, "1e999");
 -            raw_printf(p->out, "9.0e+999");
++            raw_printf(out, "9.0e+999");
            }else if( ur==0xfff0000000000000LL ){
-             raw_printf(out, "-1e999");
 -            raw_printf(p->out, "-9.0e+999");
++            raw_printf(out, "-9.0e+999");
            }else{
              sqlite3_int64 ir = (sqlite3_int64)r;
              if( r==(double)ir ){
            sqlite3_uint64 ur;
            memcpy(&ur,&r,sizeof(r));
            if( ur==0x7ff0000000000000LL ){
-             raw_printf(out, "1e999");
 -            raw_printf(p->out, "9.0e+999");
++            raw_printf(out, "9.0e+999");
            }else if( ur==0xfff0000000000000LL ){
-             raw_printf(out, "-1e999");
 -            raw_printf(p->out, "-9.0e+999");
++            raw_printf(out, "-9.0e+999");
            }else{
              sqlite3_snprintf(50,z,"%!.20g", r);
 -            raw_printf(p->out, "%s", z);
 +            raw_printf(out, "%s", z);
            }
 -        }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){
 -          const void *pBlob = sqlite3_column_blob(p->pStmt, i);
 -          int nBlob = sqlite3_column_bytes(p->pStmt, i);
 -          output_json_string(p->out, pBlob, nBlob);
 +        }else if( aiType && aiType[i]==SQLITE_BLOB && psi->pStmt ){
 +          const void *pBlob = sqlite3_column_blob(psi->pStmt, i);
 +          int nBlob = sqlite3_column_bytes(psi->pStmt, i);
 +          output_json_string(out, pBlob, nBlob);
          }else if( aiType && aiType[i]==SQLITE_TEXT ){
 -          output_json_string(p->out, azArg[i], -1);
 +          output_json_string(out, azArg[i], -1);
          }else{
 -          utf8_printf(p->out,"%s", azArg[i]);
 +          utf8_printf(out,"%s", azArg[i]);
          }
          if( i<nArg-1 ){
 -          putc(',', p->out);
 +          putc(',', out);
          }
        }
 -      putc('}', p->out);
 +      putc('}', out);
        break;
      }
      case MODE_Quote: {
@@@ -3336,31 -2938,28 +3528,32 @@@ static char *shell_error_context(const 
    if( db==0
     || zSql==0
     || (iOffset = sqlite3_error_offset(db))<0
+    || iOffset>=(int)strlen(zSql)
    ){
 -    return sqlite3_mprintf("");
 -  }
 -  while( iOffset>50 ){
 -    iOffset--;
 -    zSql++;
 -    while( (zSql[0]&0xc0)==0x80 ){ zSql++; iOffset--; }
 -  }
 -  len = strlen(zSql);
 -  if( len>78 ){
 -    len = 78;
 -    while( len>0 && (zSql[len]&0xc0)==0x80 ) len--;
 -  }
 -  zCode = sqlite3_mprintf("%.*s", len, zSql);
 -  shell_check_oom(zCode);
 -  for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; }
 -  if( iOffset<25 ){
 -    zMsg = sqlite3_mprintf("\n  %z\n  %*s^--- error here", zCode,iOffset,"");
 +    zMsg = smprintf("");
    }else{
 -    zMsg = sqlite3_mprintf("\n  %z\n  %*serror here ---^", zCode,iOffset-14,"");
 +    while( iOffset>50 ){
 +      iOffset--;
 +      zSql++;
 +      while( (zSql[0]&0xc0)==0x80 ){ zSql++; iOffset--; }
 +    }
 +    len = strlen(zSql);
 +    if( len>78 ){
 +      len = 78;
-       while( (zSql[len]&0xc0)==0x80 ) len--;
++      while( len>0 && (zSql[len]&0xc0)==0x80 ) len--;
 +    }
 +    zCode = smprintf("%.*s", len, zSql);
 +    shell_check_oom(zCode);
 +    for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; }
 +    if( iOffset<25 ){
 +      zMsg = smprintf("\n  %z\n  %*s^--- error here",
 +                             zCode, iOffset, "");
 +    }else{
 +      zMsg = smprintf("\n  %z\n  %*serror here ---^",
 +                             zCode, iOffset-14, "");
 +    }
    }
 +  shell_check_oom(zMsg);
    return zMsg;
  }
  
@@@ -3619,29 -3218,31 +3812,31 @@@ static int display_stats
              iCur);
    }
  
 -  if( pArg->pStmt ){
 +  if( psi->pStmt ){
      int iHit, iMiss;
 -    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP,
 +    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP,
                                 bReset);
 -    raw_printf(pArg->out, "Fullscan Steps:                      %d\n", iCur);
 -    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
 -    raw_printf(pArg->out, "Sort Operations:                     %d\n", iCur);
 -    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset);
 -    raw_printf(pArg->out, "Autoindex Inserts:                   %d\n", iCur);
 -    iHit = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_HIT,
 +    raw_printf(psi->out, "Fullscan Steps:                      %d\n", iCur);
 +    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
 +    raw_printf(psi->out, "Sort Operations:                     %d\n", iCur);
 +    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset);
 +    raw_printf(psi->out, "Autoindex Inserts:                   %d\n", iCur);
-     iHit = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_FILTER_HIT, bReset);
-     iMiss = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_FILTER_MISS, bReset);
++    iHit = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_FILTER_HIT,
+                                bReset);
 -    iMiss = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_MISS,
++    iMiss = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_FILTER_MISS,
+                                 bReset);
      if( iHit || iMiss ){
 -      raw_printf(pArg->out, "Bloom filter bypass taken:           %d/%d\n",
 +      raw_printf(psi->out, "Bloom filter bypass taken:           %d/%d\n",
              iHit, iHit+iMiss);
      }
 -    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);
 +    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
 +    raw_printf(psi->out, "Virtual Machine Steps:               %d\n", iCur);
 +    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset);
 +    raw_printf(psi->out, "Reprepare operations:                %d\n", iCur);
 +    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_RUN, bReset);
 +    raw_printf(psi->out, "Number of times run:                 %d\n", iCur);
 +    iCur = sqlite3_stmt_status(psi->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset);
 +    raw_printf(psi->out, "Memory used by prepared stmt:        %d\n", iCur);
    }
  
  #ifdef __linux__
@@@ -4057,28 -3532,32 +4252,33 @@@ static int ensure_shvars_table(sqlite3 
  /*
  ** Bind parameters on a prepared statement.
  **
--** Parameter bindings are taken from a TEMP table of the form:
++** Normal parameter bindings are taken from a TEMP table of the form:
  **
 -**    CREATE TEMP TABLE sqlite_parameters(key TEXT PRIMARY KEY, value)
 +**    CREATE TEMP TABLE
 +**    sqlite_parameters(key TEXT PRIMARY KEY, value, uses INT)
  **    WITHOUT ROWID;
  **
--** No bindings occur if this table does not exist.  The name of the table
--** begins with "sqlite_" so that it will not collide with ordinary application
- ** tables.  The table must be in the TEMP schema. Only rows with
 -** tables.  The table must be in the TEMP schema.
++** Normal bindings do not occur if this table does not exist.  The name of
++** the table begins with "sqlite_" so that it will not collide with ordinary
++** application tables.  The table must be in the TEMP schema. Only rows with
 +** uses=PTU_Binding are eligible for parameter binding.
++** Whether the table exists or not, parameter names like _NAN or _INF are
++** bound to the similarly named floating point "values".
  */
 -static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){
 -  int nVar;
 +static void bind_prepared_stmt(sqlite3 *db, sqlite3_stmt *pStmt){
 +  int nVar = sqlite3_bind_parameter_count(pStmt);
    int i;
    int rc;
++  int haveParams = param_table_exists(db);
    sqlite3_stmt *pQ = 0;
  
-   if( nVar==0 || !param_table_exists(db) ) return;  /* Nothing to do */
-   rc = sqlite3_prepare_v2(db,
-           "SELECT value FROM temp.sqlite_parameters"
-           " WHERE key=?1", -1, &pQ, 0);
-   if( rc || pQ==0 ) return;
 -  nVar = sqlite3_bind_parameter_count(pStmt);
+   if( nVar==0 ) return;  /* Nothing to do */
 -  if( sqlite3_table_column_metadata(pArg->db, "TEMP", "sqlite_parameters",
 -                                    "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
 -    rc = SQLITE_NOTFOUND;
 -    pQ = 0;
 -  }else{
 -    rc = sqlite3_prepare_v2(pArg->db,
 -            "SELECT value FROM temp.sqlite_parameters"
 -            " WHERE key=?1", -1, &pQ, 0);
++  if( haveParams ){
++    rc = sqlite3_prepare_v2(db,
++                            "SELECT value FROM temp.sqlite_parameters"
++                            " WHERE key=?1", -1, &pQ, 0);
++    if( rc!=SQLITE_OK || pQ==0 ) haveParams = 0;
+   }
    for(i=1; i<=nVar; i++){
      char zNum[30];
      const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
        zVar = zNum;
      }
      sqlite3_bind_text(pQ, 1, zVar, -1, SQLITE_STATIC);
-     if( sqlite3_step(pQ)==SQLITE_ROW ){
 -    if( rc==SQLITE_OK && pQ && sqlite3_step(pQ)==SQLITE_ROW ){
++    if( haveParams && sqlite3_step(pQ)==SQLITE_ROW ){
        sqlite3_bind_value(pStmt, i, sqlite3_column_value(pQ, 0));
+ #ifdef NAN
+     }else if( sqlite3_strlike("_NAN", zVar, 0)==0 ){
+       sqlite3_bind_double(pStmt, i, NAN);
+ #endif
+ #ifdef INFINITY
+     }else if( sqlite3_strlike("_INF", zVar, 0)==0 ){
+       sqlite3_bind_double(pStmt, i, INFINITY);
+ #endif
      }else{
        sqlite3_bind_null(pStmt, i);
      }
--    sqlite3_reset(pQ);
++    sqlite3_reset(pQ); /* Undocumented: NULL pQ is ok. */
    }
    sqlite3_finalize(pQ);
  }
@@@ -4358,9 -3819,9 +4566,9 @@@ static void exec_prepared_stmt_columnar
    azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
    shell_check_oom(azData);
    azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
-   shell_check_oom((void*)azNextLine);
+   shell_check_oom(azNextLine);
    memset((void*)azNextLine, 0, nColumn*sizeof(char*) );
 -  if( p->cmOpts.bQuote ){
 +  if( psi->cmOpts.bQuote ){
      azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) );
      shell_check_oom(azQuoted);
      memset(azQuoted, 0, nColumn*sizeof(char*) );
@@@ -5167,2090 -4676,1293 +5376,2004 @@@ static int run_schema_dump_query
    return rc;
  }
  
 +/* Configure help text generation to have coalesced secondary help lines
 + * with trailing newlines on all help lines. This allow help text to be
 + * representable as an array of two C-strings per dot-command.
 + */
 +DISPATCH_CONFIG[
 +  HELP_COALESCE=1
 +];
 +#define HELP_TEXT_FMTP ".%s"
 +#define HELP_TEXT_FMTS "%s"
 +/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track.
 + * Alternative is 0, ".%s\n" and "%s\n" .
 + */
 +
 +/* Forward references */
 +static int showHelp(FILE *out, const char *zPattern, ShellExState *);
 +static DotCmdRC process_input(ShellInState *psx);
 +static DotCommand *builtInCommand(int ix);
 +
  /*
 -** Text of help messages.
 +** Read the content of file zName into memory obtained from sqlite3_malloc64()
 +** and return a pointer to the buffer. The caller is responsible for freeing
 +** the memory.
  **
 -** The help text for each individual command begins with a line that starts
 -** with ".".  Subsequent lines are supplemental information.
 +** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
 +** read.
 +**
 +** For convenience, a nul-terminator byte is always appended to the data read
 +** from the file before the buffer is returned. This byte is not included in
 +** the final value of (*pnByte), if applicable.
  **
 -** There must be two or more spaces between the end of the command and the
 -** start of the description of what that command does.
 +** NULL is returned if any error is encountered. The final value of *pnByte
 +** is undefined in this case.
  */
 -static const char *(azHelp[]) = {
 -#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \
 -  && !defined(SQLITE_SHELL_FIDDLE)
 -  ".archive ...             Manage SQL archives",
 -  "   Each command must have exactly one of the following options:",
 -  "     -c, --create               Create a new archive",
 -  "     -u, --update               Add or update files with changed mtime",
 -  "     -i, --insert               Like -u but always add even if unchanged",
 -  "     -r, --remove               Remove files from archive",
 -  "     -t, --list                 List contents of archive",
 -  "     -x, --extract              Extract files from archive",
 -  "   Optional arguments:",
 -  "     -v, --verbose              Print each filename as it is processed",
 -  "     -f FILE, --file FILE       Use archive FILE (default is current db)",
 -  "     -a FILE, --append FILE     Open FILE using the apndvfs VFS",
 -  "     -C DIR, --directory DIR    Read/extract files from directory DIR",
 -  "     -g, --glob                 Use glob matching for names in archive",
 -  "     -n, --dryrun               Show the SQL that would have occurred",
 -  "   Examples:",
 -  "     .ar -cf ARCHIVE foo bar  # Create ARCHIVE from files foo and bar",
 -  "     .ar -tf ARCHIVE          # List members of ARCHIVE",
 -  "     .ar -xvf ARCHIVE         # Verbosely extract files from ARCHIVE",
 -  "   See also:",
 -  "      http://sqlite.org/cli.html#sqlite_archive_support",
 -#endif
 -#ifndef SQLITE_OMIT_AUTHORIZATION
 -  ".auth ON|OFF             Show authorizer callbacks",
 -#endif
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".backup ?DB? FILE        Backup DB (default \"main\") to FILE",
 -  "   Options:",
 -  "       --append            Use the appendvfs",
 -  "       --async             Write to FILE without journal and fsync()",
 +static char *readFile(const char *zName, int *pnByte){
 +  FILE *in = fopen(zName, "rb");
 +  long nIn;
 +  size_t nRead;
 +  char *pBuf;
++  int rc;
 +  if( in==0 ) return 0;
-   fseek(in, 0, SEEK_END);
++  rc = fseek(in, 0, SEEK_END);
++  if( rc!=0 ){
++    raw_printf(stderr, "Error: '%s' not seekable\n", zName);
++    fclose(in);
++    return 0;
++  }
 +  nIn = ftell(in);
 +  rewind(in);
 +  pBuf = sqlite3_malloc64( nIn+1 );
-   if( pBuf==0 ){ fclose(in); return 0; }
++  if( pBuf==0 ){
++    raw_printf(stderr, "Error: out of memory\n");
++    fclose(in);
++    return 0;
++  }
 +  nRead = fread(pBuf, nIn, 1, in);
 +  fclose(in);
 +  if( nRead!=1 ){
 +    sqlite3_free(pBuf);
++    raw_printf(stderr, "Error: cannot read '%s'\n", zName);
 +    return 0;
 +  }
 +  pBuf[nIn] = 0;
 +  if( pnByte ) *pnByte = nIn;
 +  return pBuf;
 +}
 +
 +#if defined(SQLITE_ENABLE_SESSION)
 +/*
 +** Close a single OpenSession object and release all of its associated
 +** resources.
 +*/
 +static void session_close(OpenSession *pSession){
 +  int i;
 +  sqlite3session_delete(pSession->p);
 +  sqlite3_free(pSession->zName);
 +  for(i=0; i<pSession->nFilter; i++){
 +    sqlite3_free(pSession->azFilter[i]);
 +  }
 +  sqlite3_free(pSession->azFilter);
 +  memset(pSession, 0, sizeof(OpenSession));
 +}
  #endif
 -  ".bail on|off             Stop after hitting an error.  Default OFF",
 -  ".binary on|off           Turn binary output on or off.  Default OFF",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".cd DIRECTORY            Change the working directory to DIRECTORY",
 +
 +/*
 +** Close all OpenSession objects and release all associated resources.
 +*/
 +#if defined(SQLITE_ENABLE_SESSION)
 +static void session_close_all(ShellInState *psi, int i){
 +  int j;
 +  struct AuxDb *pAuxDb = i<0 ? psi->pAuxDb : &psi->aAuxDb[i];
 +  for(j=0; j<pAuxDb->nSession; j++){
 +    session_close(&pAuxDb->aSession[j]);
 +  }
 +  pAuxDb->nSession = 0;
 +}
 +#else
 +# define session_close_all(X,Y)
  #endif
 -  ".changes on|off          Show number of rows changed by SQL",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".check GLOB              Fail if output since .testcase does not match",
 -  ".clone NEWDB             Clone data into NEWDB from the existing database",
 -#endif
 -  ".connection [close] [#]  Open or close an auxiliary database connection",
 -  ".databases               List names and files of attached databases",
 -  ".dbconfig ?op? ?val?     List or change sqlite3_db_config() options",
 -#if SQLITE_SHELL_HAVE_RECOVER
 -  ".dbinfo ?DB?             Show status information about the database",
 -#endif
 -  ".dump ?OBJECTS?          Render database content as SQL",
 -  "   Options:",
 -  "     --data-only            Output only INSERT statements",
 -  "     --newlines             Allow unescaped newline characters in output",
 -  "     --nosys                Omit system tables (ex: \"sqlite_stat1\")",
 -  "     --preserve-rowids      Include ROWID values in the output",
 -  "   OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
 -  "   Additional LIKE patterns can be given in subsequent arguments",
 -  ".echo on|off             Turn command echo on or off",
 -  ".eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN",
 -  "   Other Modes:",
 -#ifdef SQLITE_DEBUG
 -  "      test                  Show raw EXPLAIN QUERY PLAN output",
 -  "      trace                 Like \"full\" but enable \"PRAGMA vdbe_trace\"",
 +
 +/*
 +** Implementation of the xFilter function for an open session.  Omit
 +** any tables named by ".session filter" but let all other table through.
 +*/
 +#if defined(SQLITE_ENABLE_SESSION)
 +static int session_filter(void *pCtx, const char *zTab){
 +  OpenSession *pSession = (OpenSession*)pCtx;
 +  int i;
 +  for(i=0; i<pSession->nFilter; i++){
 +    if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
 +  }
 +  return 1;
 +}
  #endif
 -  "      trigger               Like \"full\" but also show trigger bytecode",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".excel                   Display the output of next command in spreadsheet",
 -  "   --bom                   Put a UTF8 byte-order mark on intermediate file",
 +
 +#if SHELL_DYNAMIC_EXTENSION
 +static int notify_subscribers(ShellInState *psi, NoticeKind nk, void *pvs) {
 +  int six = 0;
 +  int rcFlags = 0;
 +  ShellExState *psx = XSS(psi);
 +  while( six < psi->numSubscriptions ){
 +    struct EventSubscription *pes = psi->pSubscriptions + six++;
 +    rcFlags |= pes->eventHandler(pes->pvUserData, nk, pvs, psx);
 +  }
 +  return rcFlags;
 +}
  #endif
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".exit ?CODE?             Exit this program with return-code CODE",
 +
 +/*
 +** 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.
 +*/
 +u8 deduceDatabaseType(const char *zName, int dfltZip){
 +  FILE *f = fopen(zName, "rb");
 +  size_t n;
 +  u8 rc = SHELL_OPEN_UNSPEC;
 +  char zBuf[100];
 +  if( f==0 ){
 +    if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
 +       return SHELL_OPEN_ZIPFILE;
 +    }else{
 +       return SHELL_OPEN_NORMAL;
 +    }
 +  }
 +  n = fread(zBuf, 16, 1, f);
 +  if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){
 +    fclose(f);
 +    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 ){
 +    rc = SHELL_OPEN_APPENDVFS;
 +  }else{
 +    fseek(f, -22, SEEK_END);
 +    n = fread(zBuf, 22, 1, f);
 +    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 ){
 +      rc = SHELL_OPEN_ZIPFILE;
 +    }
 +  }
 +  fclose(f);
 +  return rc;
 +}
 +
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +/*
 +** Reconstruct an in-memory database using the output from the "dbtotxt"
 +** program.  Read content from the file in p->aAuxDb[].zDbFilename.
 +** If p->aAuxDb[].zDbFilename is 0, then read from the present input.
 +*/
 +static unsigned char *readHexDb(ShellInState *psi, int *pnData){
 +  unsigned char *a = 0;
 +  int n = 0;
 +  int pgsz = 0;
 +  int iOffset = 0;
 +  int j, k, nlError;
 +  int rc;
 +  static const char *zEndMarker = "| end ";
 +  const char *zDbFilename = psi->pAuxDb->zDbFilename;
 +  /* Need next two objects only if redirecting input to get the hex. */
 +  InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, psi->pInSource);
 +  unsigned int x[16];
 +  char zLine[1000];
 +  if( zDbFilename ){
 +    inRedir.inFile = fopen(zDbFilename, "r");
 +    if( inRedir.inFile==0 ){
 +      utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename);
 +      return 0;
 +    }
 +    psi->pInSource = &inRedir;
 +  }else{
 +    /* Will read hex DB lines inline from present input, without redirect. */
 +    if( INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
 +      printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n",
 +             psi->pInSource->zSourceSay, zEndMarker);
 +      fflush(STD_OUT);
 +    }
 +  }
 +  *pnData = 0;
 +  if( strLineGet(zLine,sizeof(zLine), psi->pInSource)==0 ) goto readHexDb_error;
 +  rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
 +  if( rc!=2 ) goto readHexDb_error;
 +  if( n<0 ) goto readHexDb_error;
 +  if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error;
 +  n = (n+pgsz-1)&~(pgsz-1);  /* Round n up to the next multiple of pgsz */
 +  a = sqlite3_malloc( n ? n : 1 );
 +  shell_check_oom(a);
 +  memset(a, 0, n);
 +  if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
 +    utf8_printf(STD_ERR, "invalid pagesize\n");
 +    goto readHexDb_error;
 +  }
 +  while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){
 +    rc = sscanf(zLine, "| page %d offset %d", &j, &k);
 +    if( rc==2 ){
 +      iOffset = k;
 +      continue;
 +    }
 +    if( cli_strncmp(zLine, zEndMarker, 6)==0 ){
 +      break;
 +    }
 +    rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
 +                &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
 +                &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
 +    if( rc==17 ){
 +      k = iOffset+j;
 +      if( k+16<=n && k>=0 ){
 +        int ii;
 +        for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
 +      }
 +    }
 +  }
 +  *pnData = n; /* Record success and size. */
 + readHexDb_cleanup:
 +  if( psi->pInSource==&inRedir ){
 +    fclose( inRedir.inFile );
 +    psi->pInSource = inRedir.pFrom;
 +  }
 +  return a;
 +
 + readHexDb_error:
 +  nlError = psi->pInSource->lineno;
 +  if( psi->pInSource!=&inRedir ){
 +    /* Since taking input inline, consume through its end marker. */
 +    while( strLineGet(zLine, sizeof(zLine), psi->pInSource)!=0 ){
 +      if(cli_strncmp(zLine, zEndMarker, 6)==0 ) break;
 +    }
 +  }
 +  sqlite3_free(a);
 +  a = 0;
 +  utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError);
 +  goto readHexDb_cleanup;
 +}
 +#endif /* SQLITE_OMIT_DESERIALIZE */
 +
- /*
- ** Scalar function "shell_int32". The first argument to this function
- ** must be a blob. The second a non-negative integer. This function
- ** reads and returns a 32-bit big-endian integer from byte
- ** offset (4*<arg2>) of the blob.
- */
- static void shellInt32(
-   sqlite3_context *context,
-   int argc,
-   sqlite3_value **argv
- ){
-   const unsigned char *pBlob;
-   int nBlob;
-   int iInt;
-   UNUSED_PARAMETER(argc);
-   nBlob = sqlite3_value_bytes(argv[0]);
-   pBlob = (const unsigned char*)sqlite3_value_blob(argv[0]);
-   iInt = sqlite3_value_int(argv[1]);
-   if( iInt>=0 && (iInt+1)*4<=nBlob ){
-     const unsigned char *a = &pBlob[iInt*4];
-     sqlite3_int64 iVal = ((sqlite3_int64)a[0]<<24)
-                        + ((sqlite3_int64)a[1]<<16)
-                        + ((sqlite3_int64)a[2]<< 8)
-                        + ((sqlite3_int64)a[3]<< 0);
-     sqlite3_result_int64(context, iVal);
-   }
- }
- /*
- ** Scalar function "shell_idquote(X)" returns string X quoted as an identifier,
- ** using "..." with internal double-quote characters doubled.
- */
- static void shellIdQuote(
-   sqlite3_context *context,
-   int argc,
-   sqlite3_value **argv
- ){
-   const char *zName = (const char*)sqlite3_value_text(argv[0]);
-   UNUSED_PARAMETER(argc);
-   if( zName ){
-     char *z = smprintf("\"%w\"", zName);
-     sqlite3_result_text(context, z, -1, sqlite3_free);
-   }
- }
 +/*
 +** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X.
 +*/
 +static void shellUSleepFunc(
 +  sqlite3_context *context,
 +  int argcUnused,
 +  sqlite3_value **argv
 +){
 +  int sleep = sqlite3_value_int(argv[0]);
 +  (void)argcUnused;
 +  sqlite3_sleep(sleep/1000);
 +  sqlite3_result_int(context, sleep);
 +}
 +
- /*
- ** Scalar function "shell_escape_crnl" used by the .recover command.
- ** The argument passed to this function is the output of built-in
- ** function quote(). If the first character of the input is "'",
- ** indicating that the value passed to quote() was a text value,
- ** then this function searches the input for "\n" and "\r" characters
- ** and adds a wrapper similar to the following:
- **
- **   replace(replace(<input>, '\n', char(10), '\r', char(13));
- **
- ** Or, if the first character of the input is not "'", then a copy
- ** of the input is returned.
- */
- static void shellEscapeCrnl(
-   sqlite3_context *context,
-   int argc,
-   sqlite3_value **argv
- ){
-   const char *zText = (const char*)sqlite3_value_text(argv[0]);
-   UNUSED_PARAMETER(argc);
-   if( zText && zText[0]=='\'' ){
-     i64 nText = sqlite3_value_bytes(argv[0]);
-     i64 i;
-     char zBuf1[20];
-     char zBuf2[20];
-     const char *zNL = 0;
-     const char *zCR = 0;
-     i64 nCR = 0;
-     i64 nNL = 0;
-     for(i=0; zText[i]; i++){
-       if( zNL==0 && zText[i]=='\n' ){
-         zNL = unused_string(zText, "\\n", "\\012", zBuf1);
-         nNL = strlen(zNL);
-       }
-       if( zCR==0 && zText[i]=='\r' ){
-         zCR = unused_string(zText, "\\r", "\\015", zBuf2);
-         nCR = strlen(zCR);
-       }
-     }
-     if( zNL || zCR ){
-       i64 iOut = 0;
-       i64 nMax = (nNL > nCR) ? nNL : nCR;
-       i64 nAlloc = nMax * nText + (nMax+64)*2;
-       char *zOut = (char*)sqlite3_malloc64(nAlloc);
-       if( zOut==0 ){
-         sqlite3_result_error_nomem(context);
-         return;
-       }
-       if( zNL && zCR ){
-         memcpy(&zOut[iOut], "replace(replace(", 16);
-         iOut += 16;
-       }else{
-         memcpy(&zOut[iOut], "replace(", 8);
-         iOut += 8;
-       }
-       for(i=0; zText[i]; i++){
-         if( zText[i]=='\n' ){
-           memcpy(&zOut[iOut], zNL, nNL);
-           iOut += nNL;
-         }else if( zText[i]=='\r' ){
-           memcpy(&zOut[iOut], zCR, nCR);
-           iOut += nCR;
-         }else{
-           zOut[iOut] = zText[i];
-           iOut++;
-         }
-       }
-       if( zNL ){
-         memcpy(&zOut[iOut], ",'", 2); iOut += 2;
-         memcpy(&zOut[iOut], zNL, nNL); iOut += nNL;
-         memcpy(&zOut[iOut], "', char(10))", 12); iOut += 12;
-       }
-       if( zCR ){
-         memcpy(&zOut[iOut], ",'", 2); iOut += 2;
-         memcpy(&zOut[iOut], zCR, nCR); iOut += nCR;
-         memcpy(&zOut[iOut], "', char(13))", 12); iOut += 12;
-       }
-       sqlite3_result_text(context, zOut, iOut, SQLITE_TRANSIENT);
-       sqlite3_free(zOut);
-       return;
-     }
-   }
-   sqlite3_result_value(context, argv[0]);
- }
 +/* Flags for open_db().
 +**
 +** The default behavior of open_db() is to exit(1) if the database fails to
 +** open.  The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
 +** but still returns without calling exit.
 +**
 +** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a
 +** ZIP archive if the file does not exist or is empty and its name matches
 +** the *.zip pattern.
 +*/
 +#define OPEN_DB_KEEPALIVE   0x001   /* Return after error if true */
 +#define OPEN_DB_ZIPFILE     0x002   /* Open as ZIP if name matches *.zip */
 +
 +/*
 +** Make sure the database is open.  If it is not, then open it.  If
 +** the database fails to open, print an error message and exit.
 +*/
 +static void open_db(ShellExState *psx, int openFlags){
 +  ShellInState *psi = ISS(psx);
 +  if( DBX(psx)==0 ){
 +    sqlite3 **pDb = &DBX(psx);
 +    const char *zDbFilename = psi->pAuxDb->zDbFilename;
 +    if( psi->openMode==SHELL_OPEN_UNSPEC ){
 +      if( zDbFilename==0 || zDbFilename[0]==0 ){
 +        psi->openMode = SHELL_OPEN_NORMAL;
 +      }else{
-         psi->openMode = deduceDatabaseType(psi->pAuxDb->zDbFilename,
++        psi->openMode = deduceDatabaseType(zDbFilename,
 +                                           (openFlags & OPEN_DB_ZIPFILE)!=0);
 +      }
 +    }
 +    switch( psi->openMode ){
 +      case SHELL_OPEN_APPENDVFS: {
 +        sqlite3_open_v2
 +          (zDbFilename, pDb,
 +           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags,
 +           "apndvfs");
 +        break;
 +      }
 +      case SHELL_OPEN_HEXDB:
 +      case SHELL_OPEN_DESERIALIZE: {
 +        sqlite3_open(0, pDb);
 +        break;
 +      }
 +      case SHELL_OPEN_ZIPFILE: {
 +        sqlite3_open(":memory:", pDb);
 +        break;
 +      }
 +      case SHELL_OPEN_READONLY: {
 +        sqlite3_open_v2(zDbFilename, pDb,
 +            SQLITE_OPEN_READONLY|psi->openFlags, 0);
 +        break;
 +      }
 +      case SHELL_OPEN_UNSPEC:
 +      case SHELL_OPEN_NORMAL: {
 +        sqlite3_open_v2(zDbFilename, pDb,
 +           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, 0);
 +        break;
 +      }
 +    }
 +    globalDb = DBX(psx);
 +    if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
 +      const char *zWhy = (DBX(psx)==0)? "(?)" : sqlite3_errmsg(DBX(psx));
 +      utf8_printf(STD_ERR,"Error: unable to open database \"%s\": %s\n",
 +          zDbFilename, zWhy);
-       if( openFlags & OPEN_DB_KEEPALIVE ){
-         sqlite3_open(":memory:", pDb);
-         globalDb = *pDb;
-         return;
++      if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
++        exit(1);
++      }
++      sqlite3_close(DBX(psx));
++      sqlite3_open(":memory:", &DBX(psx));
++      if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
++        utf8_printf(stderr,
++          "Also: unable to open substitute in-memory database.\n"
++        );
++        exit(1);
++      }else{
++        utf8_printf(stderr,
++          "Notice: using substitute in-memory database instead of \"%s\"\n",
++          zDbFilename);
 +      }
-       exit(1);
++    }
++    sqlite3_db_config(globalDb, SQLITE_DBCONFIG_STMT_SCANSTATUS,(int)0,(int*)0);
++
++    /* Reflect the use or absence of --unsafe-testing invocation. */
++    {
++      int testmode_on = ShellHasFlag(psi,SHFLG_TestingMode);
++      sqlite3_db_config(globalDb, SQLITE_DBCONFIG_TRUSTED_SCHEMA,testmode_on,0);
++      sqlite3_db_config(globalDb, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
 +    }
 +#ifndef SQLITE_OMIT_LOAD_EXTENSION
 +    sqlite3_enable_load_extension(globalDb, 1);
  #endif
 -  ".expert                  EXPERIMENTAL. Suggest indexes for queries",
 -  ".explain ?on|off|auto?   Change the EXPLAIN formatting mode.  Default: auto",
 -  ".filectrl CMD ...        Run various sqlite3_file_control() operations",
 -  "   --schema SCHEMA         Use SCHEMA instead of \"main\"",
 -  "   --help                  Show CMD details",
 -  ".fullschema ?--indent?   Show schema and the content of sqlite_stat tables",
 -  ".headers on|off          Turn display of headers on or off",
 -  ".help ?-all? ?PATTERN?   Show help text for PATTERN",
 +    sqlite3_shathree_init(globalDb, 0, 0);
 +    sqlite3_uint_init(globalDb, 0, 0);
 +    sqlite3_decimal_init(globalDb, 0, 0);
++    sqlite3_base64_init(p->db, 0, 0);
++    sqlite3_base85_init(p->db, 0, 0);
 +    sqlite3_regexp_init(globalDb, 0, 0);
 +    sqlite3_ieee_init(globalDb, 0, 0);
 +    sqlite3_series_init(globalDb, 0, 0);
  #ifndef SQLITE_SHELL_FIDDLE
 -  ".import FILE TABLE       Import data from FILE into TABLE",
 -  "   Options:",
 -  "     --ascii               Use \\037 and \\036 as column and row separators",
 -  "     --csv                 Use , and \\n as column and row separators",
 -  "     --skip N              Skip the first N rows of input",
 -  "     --schema S            Target table to be S.TABLE",
 -  "     -v                    \"Verbose\" - increase auxiliary output",
 -  "   Notes:",
 -  "     *  If TABLE does not exist, it is created.  The first row of input",
 -  "        determines the column names.",
 -  "     *  If neither --csv or --ascii are used, the input mode is derived",
 -  "        from the \".mode\" output mode",
 -  "     *  If FILE begins with \"|\" then it is a command that generates the",
 -  "        input text.",
 -#endif
 -#ifndef SQLITE_OMIT_TEST_CONTROL
 -  ",imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
 -#endif
 -  ".indexes ?TABLE?         Show names of indexes",
 -  "                           If TABLE is specified, only show indexes for",
 -  "                           tables matching TABLE using the LIKE operator.",
 -#ifdef SQLITE_ENABLE_IOTRACE
 -  ",iotrace FILE            Enable I/O diagnostic logging to FILE",
 +    sqlite3_fileio_init(globalDb, 0, 0);
 +    sqlite3_completion_init(globalDb, 0, 0);
  #endif
 -  ".limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT",
 -  ".lint OPTIONS            Report potential schema issues.",
 -  "     Options:",
 -  "        fkey-indexes     Find missing foreign key indexes",
 -#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
 -  ".load FILE ?ENTRY?       Load an extension library",
 +#if SQLITE_SHELL_HAVE_RECOVER
 +    sqlite3_dbdata_init(globalDb, 0, 0);
  #endif
 -#if !defined(SQLITE_SHELL_FIDDLE)
 -  ".log FILE|on|off         Turn logging on or off.  FILE can be stderr/stdout",
 -#else
 -  ".log on|off              Turn logging on or off.",
 +#ifdef SQLITE_HAVE_ZLIB
 +    if( !psi->bSafeModeFuture ){
 +      sqlite3_zipfile_init(globalDb, 0, 0);
 +      sqlite3_sqlar_init(globalDb, 0, 0);
 +    }
  #endif
 -  ".mode MODE ?OPTIONS?     Set output mode",
 -  "   MODE is one of:",
 -  "     ascii       Columns/rows delimited by 0x1F and 0x1E",
 -  "     box         Tables using unicode box-drawing characters",
 -  "     csv         Comma-separated values",
 -  "     column      Output in columns.  (See .width)",
 -  "     html        HTML <table> code",
 -  "     insert      SQL insert statements for TABLE",
 -  "     json        Results in a JSON array",
 -  "     line        One value per line",
 -  "     list        Values delimited by \"|\"",
 -  "     markdown    Markdown table format",
 -  "     qbox        Shorthand for \"box --wrap 60 --quote\"",
 -  "     quote       Escape answers as for SQL",
 -  "     table       ASCII-art table",
 -  "     tabs        Tab-separated values",
 -  "     tcl         TCL list elements",
 -  "   OPTIONS: (for columnar modes or insert mode):",
 -  "     --wrap N       Wrap output lines to no longer than N characters",
 -  "     --wordwrap B   Wrap or not at word boundaries per B (on/off)",
 -  "     --ww           Shorthand for \"--wordwrap 1\"",
 -  "     --quote        Quote output text as SQL literals",
 -  "     --noquote      Do not quote output text",
 -  "     TABLE          The name of SQL table used for \"insert\" mode",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".nonce STRING            Suspend safe mode for one command if nonce matches",
 +
 +#ifdef SQLITE_SHELL_EXTFUNCS
 +    /* Create a preprocessing mechanism for extensions to make
 +     * their own provisions for being built into the shell.
 +     * This is a short-span macro. See further below for usage.
 +     */
 +#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant
 +#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant)
 +    /* Let custom-included extensions get their ..._init() called.
 +     * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause
 +     * the extension's sqlite3_*_init( db, pzErrorMsg, pApi )
 +     * inititialization routine to be called.
 +     */
 +    {
 +      int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db);
 +    /* Let custom-included extensions expose their functionality.
 +     * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause
 +     * the SQL functions, virtual tables, collating sequences or
 +     * VFS's implemented by the extension to be registered.
 +     */
 +      if( irc==SQLITE_OK
 +          || irc==SQLITE_OK_LOAD_PERMANENTLY ){
 +        SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0);
 +      }
 +#undef SHELL_SUB_MACRO
 +#undef SHELL_SUBMACRO
 +    }
  #endif
 -  ".nullvalue STRING        Use STRING in place of NULL values",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE",
 -  "     If FILE begins with '|' then open as a pipe",
 -  "       --bom  Put a UTF8 byte-order mark at the beginning",
 -  "       -e     Send output to the system text editor",
 -  "       -x     Send output as CSV to a spreadsheet (same as \".excel\")",
 -  /* Note that .open is (partially) available in WASM builds but is
 -  ** currently only intended to be used by the fiddle tool, not
 -  ** end users, so is "undocumented." */
 -  ".open ?OPTIONS? ?FILE?   Close existing database and reopen FILE",
 -  "     Options:",
 -  "        --append        Use appendvfs to append database to the end of FILE",
 +
 +    sqlite3_create_function(globalDb, "shell_add_schema", 3, SQLITE_UTF8, 0,
 +                            shellAddSchemaName, 0, 0);
 +    sqlite3_create_function(globalDb, "shell_module_schema", 1, SQLITE_UTF8, 0,
 +                            shellModuleSchema, 0, 0);
 +    sqlite3_create_function(globalDb, "shell_putsnl", 1, SQLITE_UTF8, psx,
 +                            shellPutsFunc, 0, 0);
-     sqlite3_create_function(globalDb, "shell_escape_crnl", 1, SQLITE_UTF8, 0,
-                             shellEscapeCrnl, 0, 0);
-     sqlite3_create_function(globalDb, "shell_int32", 2, SQLITE_UTF8, 0,
-                             shellInt32, 0, 0);
-     sqlite3_create_function(globalDb, "shell_idquote", 1, SQLITE_UTF8, 0,
-                             shellIdQuote, 0, 0);
 +    sqlite3_create_function(globalDb, "usleep",1,SQLITE_UTF8, 0,
 +                            shellUSleepFunc, 0, 0);
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +    sqlite3_create_function(globalDb, "edit", 1, SQLITE_UTF8, 0,
 +                            editFunc, 0, 0);
 +    sqlite3_create_function(globalDb, "edit", 2, SQLITE_UTF8, 0,
 +                            editFunc, 0, 0);
  #endif
 +    if( psi->openMode==SHELL_OPEN_ZIPFILE ){
 +      char *zSql = smprintf("CREATE VIRTUAL TABLE zip USING zipfile(%Q);",
 +                            zDbFilename);
 +      shell_check_oom(zSql);
 +      sqlite3_exec(DBX(psx), zSql, 0, 0, 0);
 +      sqlite3_free(zSql);
 +    }
  #ifndef SQLITE_OMIT_DESERIALIZE
 -  "        --deserialize   Load into memory using sqlite3_deserialize()",
 -  "        --hexdb         Load the output of \"dbtotxt\" as an in-memory db",
 -  "        --maxsize N     Maximum size for --hexdb or --deserialized database",
 -#endif
 -  "        --new           Initialize FILE to an empty database",
 -  "        --nofollow      Do not follow symbolic links",
 -  "        --readonly      Open FILE readonly",
 -  "        --zip           FILE is a ZIP archive",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".output ?FILE?           Send output to FILE or stdout if FILE is omitted",
 -  "   If FILE begins with '|' then open it as a pipe.",
 -  "   Options:",
 -  "     --bom                 Prefix output with a UTF8 byte-order mark",
 -  "     -e                    Send output to the system text editor",
 -  "     -x                    Send output as CSV to a spreadsheet",
 -#endif
 -  ".parameter CMD ...       Manage SQL parameter bindings",
 -  "   clear                   Erase all bindings",
 -  "   init                    Initialize the TEMP table that holds bindings",
 -  "   list                    List the current parameter bindings",
 -  "   set PARAMETER VALUE     Given SQL parameter PARAMETER a value of VALUE",
 -  "                           PARAMETER should start with one of: $ : @ ?",
 -  "   unset PARAMETER         Remove PARAMETER from the binding table",
 -  ".print STRING...         Print literal STRING",
 -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
 -  ".progress N              Invoke progress handler after every N opcodes",
 -  "   --limit N                 Interrupt after N progress callbacks",
 -  "   --once                    Do no more than one progress interrupt",
 -  "   --quiet|-q                No output except at interrupts",
 -  "   --reset                   Reset the count for each input and interrupt",
 -#endif
 -  ".prompt MAIN CONTINUE    Replace the standard prompts",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".quit                    Stop interpreting input stream, exit if primary.",
 -  ".read FILE               Read input from FILE or command output",
 -  "    If FILE begins with \"|\", it is a command that generates the input.",
 -#endif
 -#if SQLITE_SHELL_HAVE_RECOVER
 -  ".recover                 Recover as much data as possible from corrupt db.",
 -  "   --ignore-freelist        Ignore pages that appear to be on db freelist",
 -  "   --lost-and-found TABLE   Alternative name for the lost-and-found table",
 -  "   --no-rowids              Do not attempt to recover rowid values",
 -  "                            that are not also INTEGER PRIMARY KEYs",
 -#endif
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".restore ?DB? FILE       Restore content of DB (default \"main\") from FILE",
 -  ".save ?OPTIONS? FILE     Write database to FILE (an alias for .backup ...)",
 -#endif
 -  ".scanstats on|off|est    Turn sqlite3_stmt_scanstatus() metrics on or off",
 -  ".schema ?PATTERN?        Show the CREATE statements matching PATTERN",
 -  "   Options:",
 -  "      --indent             Try to pretty-print the schema",
 -  "      --nosys              Omit objects whose names start with \"sqlite_\"",
 -  ",selftest ?OPTIONS?      Run tests defined in the SELFTEST table",
 -  "    Options:",
 -  "       --init               Create a new SELFTEST table",
 -  "       -v                   Verbose output",
 -  ".separator COL ?ROW?     Change the column and row separators",
 -#if defined(SQLITE_ENABLE_SESSION)
 -  ".session ?NAME? CMD ...  Create or control sessions",
 -  "   Subcommands:",
 -  "     attach TABLE             Attach TABLE",
 -  "     changeset FILE           Write a changeset into FILE",
 -  "     close                    Close one session",
 -  "     enable ?BOOLEAN?         Set or query the enable bit",
 -  "     filter GLOB...           Reject tables matching GLOBs",
 -  "     indirect ?BOOLEAN?       Mark or query the indirect status",
 -  "     isempty                  Query whether the session is empty",
 -  "     list                     List currently open session names",
 -  "     open DB NAME             Open a new session on DB",
 -  "     patchset FILE            Write a patchset into FILE",
 -  "   If ?NAME? is omitted, the first defined session is used.",
 -#endif
 -  ".sha3sum ...             Compute a SHA3 hash of database content",
 -  "    Options:",
 -  "      --schema              Also hash the sqlite_schema table",
 -  "      --sha3-224            Use the sha3-224 algorithm",
 -  "      --sha3-256            Use the sha3-256 algorithm (default)",
 -  "      --sha3-384            Use the sha3-384 algorithm",
 -  "      --sha3-512            Use the sha3-512 algorithm",
 -  "    Any other argument is a LIKE pattern for tables to hash",
 -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 -  ".shell CMD ARGS...       Run CMD ARGS... in a system shell",
 -#endif
 -  ".show                    Show the current values for various settings",
 -  ".stats ?ARG?             Show stats or turn stats on or off",
 -  "   off                      Turn off automatic stat display",
 -  "   on                       Turn on automatic stat display",
 -  "   stmt                     Show statement stats",
 -  "   vmstep                   Show the virtual machine step count only",
 -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 -  ".system CMD ARGS...      Run CMD ARGS... in a system shell",
 +    else
 +    if( psi->openMode==SHELL_OPEN_DESERIALIZE
 +        || psi->openMode==SHELL_OPEN_HEXDB ){
 +      int rc;
 +      int nData = 0;
 +      unsigned char *aData;
 +      if( psi->openMode==SHELL_OPEN_DESERIALIZE ){
 +        aData = (unsigned char*)readFile(zDbFilename, &nData);
 +      }else{
 +        aData = readHexDb(psi, &nData);
 +        if( aData==0 ){
 +          return;
 +        }
 +      }
 +      rc = sqlite3_deserialize(DBX(psx), "main", aData, nData, nData,
 +                   SQLITE_DESERIALIZE_RESIZEABLE |
 +                   SQLITE_DESERIALIZE_FREEONCLOSE);
 +      if( rc ){
 +        utf8_printf(STD_ERR, "Error: sqlite3_deserialize() returns %d\n", rc);
 +      }
 +      if( psi->szMax>0 ){
 +        sqlite3_file_control(DBX(psx), "main", SQLITE_FCNTL_SIZE_LIMIT,
 +                             &psi->szMax);
 +      }
 +    }
  #endif
-     if( psi->bSafeModeFuture && DBX(psx)!=0 ){
-       sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx);
 -  ".tables ?TABLE?          List names of tables matching LIKE pattern TABLE",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ",testcase NAME           Begin redirecting output to 'testcase-out.txt'",
++    if( DBX(psx)!=0 ){
++      if( psi->bSafeModeFuture ){
++        sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx);
++      }
++      sqlite3_db_config(
++           DBX(psx), SQLITE_DBCONFIG_STMT_SCANSTATUS, psi->scanstatsOn,(int*)0);
 +    }
 +#if SHELL_DYNAMIC_EXTENSION
 +    notify_subscribers(psi, NK_DbUserAppeared, DBX(psx));
  #endif
 -  ",testctrl CMD ...        Run various sqlite3_test_control() operations",
 -  "                           Run \".testctrl\" with no arguments for details",
 -  ".timeout MS              Try opening locked tables for MS milliseconds",
 -  ".timer on|off            Turn SQL timer on or off",
 -#ifndef SQLITE_OMIT_TRACE
 -  ".trace ?OPTIONS?         Output each SQL statement as it is run",
 -  "    FILE                    Send output to FILE",
 -  "    stdout                  Send output to stdout",
 -  "    stderr                  Send output to stderr",
 -  "    off                     Disable tracing",
 -  "    --expanded              Expand query parameters",
 -#ifdef SQLITE_ENABLE_NORMALIZE
 -  "    --normalized            Normal the SQL statements",
 +  }
 +}
 +
 +/*
 +** Attempt to close the database connection. Report errors.
 +*/
 +void close_db(sqlite3 *db){
 +  int rc;
 +  if( db==globalDb ){
 +    sqlite3_mutex_enter(pGlobalDbLock);
 +    globalDb = 0;
 +    rc = sqlite3_close(db);
 +    sqlite3_mutex_leave(pGlobalDbLock);
 +  }else{
 +    rc = sqlite3_close(db);
 +  }
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n",
 +        rc, sqlite3_errmsg(db));
 +  }
 +}
 +
 +#if HAVE_READLINE || HAVE_EDITLINE
 +/*
 +** Readline completion callbacks
 +*/
 +static char *readline_completion_generator(const char *text, int state){
 +  static sqlite3_stmt *pStmt = 0;
 +  char *zRet;
 +  if( state==0 ){
 +    char *zSql;
 +    sqlite3_finalize(pStmt);
 +    zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase"
 +                    " FROM completion(%Q) ORDER BY 1", text);
 +    shell_check_oom(zSql);
 +    sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
 +    sqlite3_free(zSql);
 +  }
 +  if( sqlite3_step(pStmt)==SQLITE_ROW ){
 +    const char *z = (const char*)sqlite3_column_text(pStmt,0);
 +    if( z!=0 ){
 +      zRet = strdup(z);
 +      shell_check_oom(zRet);
 +    }
 +  }else{
 +    sqlite3_finalize(pStmt);
 +    pStmt = 0;
 +    zRet = 0;
 +  }
 +  return zRet;
 +}
 +static char **readline_completion(const char *zText, int iStart, int iEnd){
++  (void)iStart;
++  (void)iEnd;
 +  rl_attempted_completion_over = 1;
 +  return rl_completion_matches(zText, readline_completion_generator);
 +}
 +
 +#elif HAVE_LINENOISE
 +/*
 +** Linenoise completion callback
 +*/
 +static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
 +  i64 nLine = strlen(zLine);
 +  i64 i, iStart;
 +  sqlite3_stmt *pStmt = 0;
 +  char *zSql;
 +  char zBuf[1000];
 +
-   if( nLine>sizeof(zBuf)-30 ) return;
++  if( nLine>(i64)sizeof(zBuf)-30 ) return;
 +  if( zLine[0]=='.' || zLine[0]=='#') return;
 +  for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
 +  if( i==nLine-1 ) return;
 +  iStart = i+1;
 +  memcpy(zBuf, zLine, iStart);
 +  zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase"
 +                  " FROM completion(%Q,%Q) ORDER BY 1",
 +                  &zLine[iStart], zLine);
 +  shell_check_oom(zSql);
 +  sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
 +  sqlite3_free(zSql);
 +  sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
 +  while( sqlite3_step(pStmt)==SQLITE_ROW ){
 +    const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
 +    int nCompletion = sqlite3_column_bytes(pStmt, 0);
-     if( iStart+nCompletion < sizeof(zBuf)-1 && zCompletion ){
++    if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){
 +      memcpy(zBuf+iStart, zCompletion, nCompletion+1);
 +      linenoiseAddCompletion(lc, zBuf);
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +}
  #endif
 -  "    --plain                 Show SQL as it is input",
 -  "    --stmt                  Trace statement execution (SQLITE_TRACE_STMT)",
 -  "    --profile               Profile statements (SQLITE_TRACE_PROFILE)",
 -  "    --row                   Trace each row (SQLITE_TRACE_ROW)",
 -  "    --close                 Trace connection close (SQLITE_TRACE_CLOSE)",
 -#endif /* SQLITE_OMIT_TRACE */
 -#ifdef SQLITE_DEBUG
 -  ".unmodule NAME ...       Unregister virtual table modules",
 -  "    --allexcept             Unregister everything except those named",
 +
 +/*
 +** Do C-language style escape sequence translation.
 +**
 +**    \a    -> alarm
 +**    \b    -> backspace
 +**    \t    -> tab
 +**    \n    -> newline
 +**    \v    -> vertical tab
 +**    \f    -> form feed
 +**    \r    -> carriage return
 +**    \s    -> space
 +**    \"    -> "
 +**    \'    -> '
 +**    \\    -> backslash
 +**    \NNN  -> ascii character NNN in octal
++**    \xHH  -> ascii character HH in hexadecimal
 +*/
 +static void resolve_backslashes(char *z){
 +  int i, j;
 +  char c;
 +  while( *z && *z!='\\' ) z++;
 +  for(i=j=0; (c = z[i])!=0; i++, j++){
 +    if( c=='\\' && z[i+1]!=0 ){
 +      c = z[++i];
 +      if( c=='a' ){
 +        c = '\a';
 +      }else if( c=='b' ){
 +        c = '\b';
 +      }else if( c=='t' ){
 +        c = '\t';
 +      }else if( c=='n' ){
 +        c = '\n';
 +      }else if( c=='v' ){
 +        c = '\v';
 +      }else if( c=='f' ){
 +        c = '\f';
 +      }else if( c=='r' ){
 +        c = '\r';
 +      }else if( c=='"' ){
 +        c = '"';
 +      }else if( c=='\'' ){
 +        c = '\'';
 +      }else if( c=='\\' ){
 +        c = '\\';
++      }else if( c=='x' ){
++        int nhd = 0, hdv;
++        u8 hv = 0;
++        while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){
++          hv = (u8)((hv<<4)|hdv);
++          ++nhd;
++        }
++        i += nhd;
++        c = hv;
 +      }else if( c>='0' && c<='7' ){
 +        c -= '0';
 +        if( z[i+1]>='0' && z[i+1]<='7' ){
 +          i++;
 +          c = (c<<3) + z[i] - '0';
 +          if( z[i+1]>='0' && z[i+1]<='7' ){
 +            i++;
 +            c = (c<<3) + z[i] - '0';
 +          }
 +        }
 +      }
 +    }
 +    z[j] = c;
 +  }
 +  if( j<i ) z[j] = 0;
 +}
 +
 +/*
 +** Interpret zArg as either an integer or a boolean value.  Return 1 or 0
 +** for TRUE and FALSE.  Return the integer value if appropriate.
 +*/
 +static int booleanValue(const char *zArg){
 +  static const char *zBoolNames[] = {
 +    "no","yes", "off","on",
 +#ifdef BOOLNAMES_ARE_BOOLEAN
 +    "false","true",
  #endif
 -  ".version                 Show source, library and compiler versions",
 -  ".vfsinfo ?AUX?           Information about the top-level VFS",
 -  ".vfslist                 List all available VFSes",
 -  ".vfsname ?AUX?           Print the name of the VFS stack",
 -  ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
 -  "     Negative values right-justify",
 -};
 +    0
 +  };
 +  int i;
 +  if( zArg[0]=='0' && zArg[1]=='x' ){
 +    for(i=2; hexDigitValue(zArg[i])>=0; i++){}
 +  }else{
 +    for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
 +  }
 +  if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
 +  for( i=0; zBoolNames[i]!=0; ++i ){
 +    if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1;
 +  }
 +  utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
 +              zArg);
 +  return 0;
 +}
 +
 +/*
 +** Set or clear a shell flag according to a boolean value.
 +*/
 +static void setOrClearFlag(ShellExState *psx, unsigned mFlag, const char *zArg){
 +  if( booleanValue(zArg) ){
 +    ShellSetFlag(psx, mFlag);
 +  }else{
 +    ShellClearFlag(psx, mFlag);
 +  }
 +}
 +
 +/*
 +** Close an output file, provided it is not stderr or stdout
 +*/
 +static void output_file_close(FILE *f){
 +  if( f && f!=STD_OUT && f!=STD_ERR ) fclose(f);
 +}
 +
 +/*
 +** Try to open an output file.   The names "stdout" and "stderr" are
 +** recognized and do the right thing.  NULL is returned if the output
 +** filename is "off".
 +*/
 +static FILE *output_file_open(const char *zFile, int bTextMode){
 +  FILE *f;
 +  if( cli_strcmp(zFile,"stdout")==0 ){
 +    f = STD_OUT;
 +  }else if( cli_strcmp(zFile, "stderr")==0 ){
 +    f = STD_ERR;
 +  }else if( cli_strcmp(zFile, "off")==0 ){
 +    f = 0;
 +  }else{
 +    f = fopen(zFile, bTextMode ? "w" : "wb");
 +    if( f==0 ){
 +      utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", zFile);
 +    }
 +  }
 +  return f;
 +}
  
 +#ifndef SQLITE_OMIT_TRACE
  /*
 -** Output help text.
 -**
 -** zPattern describes the set of commands for which help text is provided.
 -** If zPattern is NULL, then show all commands, but only give a one-line
 -** description of each.
 -**
 -** Return the number of matches.
 +** A routine for handling output from sqlite3_trace().
  */
 -static int showHelp(FILE *out, const char *zPattern){
 -  int i = 0;
 -  int j = 0;
 -  int n = 0;
 -  char *zPat;
 -  if( zPattern==0
 -   || zPattern[0]=='0'
 -   || cli_strcmp(zPattern,"-a")==0
 -   || cli_strcmp(zPattern,"-all")==0
 -   || cli_strcmp(zPattern,"--all")==0
 -  ){
 -    enum HelpWanted { HW_NoCull = 0, HW_SummaryOnly = 1, HW_Undoc = 2 };
 -    enum HelpHave { HH_Undoc = 2, HH_Summary = 1, HH_More = 0 };
 -    /* Show all or most commands
 -    ** *zPattern==0   => summary of documented commands only
 -    ** *zPattern=='0' => whole help for undocumented commands
 -    ** Otherwise      => whole help for documented commands
 -    */
 -    enum HelpWanted hw = HW_SummaryOnly;
 -    enum HelpHave hh = HH_More;
 -    if( zPattern!=0 ){
 -      hw = (*zPattern=='0')? HW_NoCull|HW_Undoc : HW_NoCull;
 -    }
 -    for(i=0; i<ArraySize(azHelp); i++){
 -      switch( azHelp[i][0] ){
 -      case ',':
 -        hh = HH_Summary|HH_Undoc;
 -        break;
 -      case '.':
 -        hh = HH_Summary;
 -        break;
 -      default:
 -        hh &= ~HH_Summary;
 +static int sql_trace_callback(
 +  unsigned mType,         /* The trace type */
 +  void *pArg,             /* The shell state pointer */
 +  void *pP,               /* Usually a pointer to sqlite_stmt */
 +  void *pX                /* Auxiliary output */
 +){
 +  ShellInState *psi = (ShellInState*)pArg;
 +  sqlite3_stmt *pStmt;
 +  const char *zSql;
 +  i64 nSql;
 +  if( psi->traceOut==0 ) return 0;
 +  if( mType==SQLITE_TRACE_CLOSE ){
 +    utf8_printf(psi->traceOut, "-- closing database connection\n");
 +    return 0;
 +  }
-   if( mType!=SQLITE_TRACE_ROW && ((const char*)pX)[0]=='-' ){
++  if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){
 +    zSql = (const char*)pX;
 +  }else{
 +    pStmt = (sqlite3_stmt*)pP;
 +    switch( psi->eTraceType ){
 +      case SHELL_TRACE_EXPANDED: {
 +        zSql = sqlite3_expanded_sql(pStmt);
          break;
        }
 -      if( ((hw^hh)&HH_Undoc)==0 ){
 -        if( (hh&HH_Summary)!=0 ){
 -          utf8_printf(out, ".%s\n", azHelp[i]+1);
 -          ++n;
 -        }else if( (hw&HW_SummaryOnly)==0 ){
 -          utf8_printf(out, "%s\n", azHelp[i]);
 -        }
 +#ifdef SQLITE_ENABLE_NORMALIZE
 +      case SHELL_TRACE_NORMALIZED: {
 +        zSql = sqlite3_normalized_sql(pStmt);
 +        break;
        }
 -    }
 -  }else{
 -    /* Seek documented commands for which zPattern is an exact prefix */
 -    zPat = sqlite3_mprintf(".%s*", zPattern);
 -    shell_check_oom(zPat);
 -    for(i=0; i<ArraySize(azHelp); i++){
 -      if( sqlite3_strglob(zPat, azHelp[i])==0 ){
 -        utf8_printf(out, "%s\n", azHelp[i]);
 -        j = i+1;
 -        n++;
 +#endif
 +      default: {
 +        zSql = sqlite3_sql(pStmt);
 +        break;
        }
      }
 -    sqlite3_free(zPat);
 -    if( n ){
 -      if( n==1 ){
 -        /* when zPattern is a prefix of exactly one command, then include
 -        ** the details of that command, which should begin at offset j */
 -        while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){
 -          utf8_printf(out, "%s\n", azHelp[j]);
 -          j++;
 -        }
 -      }
 -      return n;
 +  }
 +  if( zSql==0 ) return 0;
 +  nSql = strlen(zSql);
 +  if( nSql>1000000000 ) nSql = 1000000000; /* clamp to 1 billion */
 +  while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; }
 +  switch( mType ){
 +    case SQLITE_TRACE_ROW:
 +    case SQLITE_TRACE_STMT: {
 +      utf8_printf(psi->traceOut, "%.*s;\n", (int)nSql, zSql);
 +      break;
      }
 -    /* Look for documented commands that contain zPattern anywhere.
 -    ** Show complete text of all documented commands that match. */
 -    zPat = sqlite3_mprintf("%%%s%%", zPattern);
 -    shell_check_oom(zPat);
 -    for(i=0; i<ArraySize(azHelp); i++){
 -      if( azHelp[i][0]==',' ){
 -        while( i<ArraySize(azHelp)-1 && azHelp[i+1][0]==' ' ) ++i;
 -        continue;
 -      }
 -      if( azHelp[i][0]=='.' ) j = i;
 -      if( sqlite3_strlike(zPat, azHelp[i], 0)==0 ){
 -        utf8_printf(out, "%s\n", azHelp[j]);
 -        while( j<ArraySize(azHelp)-1 && azHelp[j+1][0]==' ' ){
 -          j++;
 -          utf8_printf(out, "%s\n", azHelp[j]);
 -        }
 -        i = j;
 -        n++;
 -      }
 +    case SQLITE_TRACE_PROFILE: {
-       sqlite3_int64 nNanosec = *(sqlite3_int64*)pX;
++      sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0;
 +      utf8_printf(psi->traceOut,"%.*s; -- %lld ns\n", (int)nSql,zSql,nNanosec);
 +      break;
      }
 -    sqlite3_free(zPat);
    }
 -  return n;
 +  return 0;
  }
 -
 -/* Forward reference */
 -static int process_input(ShellState *p);
 +#endif
  
  /*
- ** A no-op routine that runs with the ".breakpoint" doc-command.
 -** Read the content of file zName into memory obtained from sqlite3_malloc64()
 -** and return a pointer to the buffer. The caller is responsible for freeing
 -** the memory.
 -**
 -** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
 -** read.
 -**
 -** For convenience, a nul-terminator byte is always appended to the data read
 -** from the file before the buffer is returned. This byte is not included in
 -** the final value of (*pnByte), if applicable.
++** A no-op routine that runs with the ".breakpoint" dot-command.
 +** This is a useful spot to set a debugger breakpoint.
+ **
 -** NULL is returned if any error is encountered. The final value of *pnByte
 -** is undefined in this case.
++** This routine does not do anything practical.  The code are there simply
++** to prevent the compiler from optimizing this routine out.
  */
 -static char *readFile(const char *zName, int *pnByte){
 -  FILE *in = fopen(zName, "rb");
 -  long nIn;
 -  size_t nRead;
 -  char *pBuf;
 -  int rc;
 -  if( in==0 ) return 0;
 -  rc = fseek(in, 0, SEEK_END);
 -  if( rc!=0 ){
 -    raw_printf(stderr, "Error: '%s' not seekable\n", zName);
 -    fclose(in);
 -    return 0;
 -  }
 -  nIn = ftell(in);
 -  rewind(in);
 -  pBuf = sqlite3_malloc64( nIn+1 );
 -  if( pBuf==0 ){
 -    raw_printf(stderr, "Error: out of memory\n");
 -    fclose(in);
 -    return 0;
 -  }
 -  nRead = fread(pBuf, nIn, 1, in);
 -  fclose(in);
 -  if( nRead!=1 ){
 -    sqlite3_free(pBuf);
 -    raw_printf(stderr, "Error: cannot read '%s'\n", zName);
 -    return 0;
 -  }
 -  pBuf[nIn] = 0;
 -  if( pnByte ) *pnByte = nIn;
 -  return pBuf;
 +static void test_breakpoint(void){
 +  static int nCall = 0;
-   nCall++;
++  if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n");
  }
  
 -#if defined(SQLITE_ENABLE_SESSION)
  /*
 -** Close a single OpenSession object and release all of its associated
 -** resources.
 +** An object used to read a CSV and other files for import.
  */
 -static void session_close(OpenSession *pSession){
 -  int i;
 -  sqlite3session_delete(pSession->p);
 -  sqlite3_free(pSession->zName);
 -  for(i=0; i<pSession->nFilter; i++){
 -    sqlite3_free(pSession->azFilter[i]);
 +typedef struct ImportCtx ImportCtx;
 +struct ImportCtx {
 +  const char *zFile;  /* Name of the input file */
 +  FILE *in;           /* Read the CSV text from this input stream */
 +  int (SQLITE_CDECL *xCloser)(FILE*);      /* Func to close in */
 +  char *z;            /* Accumulated text for a field */
 +  int n;              /* Number of bytes in z */
 +  int nAlloc;         /* Space allocated for z[] */
 +  int nLine;          /* Current line number */
 +  int nRow;           /* Number of rows imported */
 +  int nErr;           /* Number of errors encountered */
 +  int bNotFirst;      /* True if one or more bytes already read */
 +  int cTerm;          /* Character that terminated the most recent field */
 +  int cColSep;        /* The column separator character.  (Usually ",") */
 +  int cRowSep;        /* The row separator character.  (Usually "\n") */
 +};
 +
 +/* Clean up resourced used by an ImportCtx */
 +static void import_cleanup(ImportCtx *p){
 +  if( p->in!=0 && p->xCloser!=0 ){
 +    p->xCloser(p->in);
 +    p->in = 0;
    }
 -  sqlite3_free(pSession->azFilter);
 -  memset(pSession, 0, sizeof(OpenSession));
 +  sqlite3_free(p->z);
 +  p->z = 0;
  }
 -#endif
  
 -/*
 -** Close all OpenSession objects and release all associated resources.
 +/* Append a single byte to z[] */
 +static void import_append_char(ImportCtx *p, int c){
 +  if( p->n+1>=p->nAlloc ){
 +    p->nAlloc += p->nAlloc + 100;
 +    p->z = sqlite3_realloc64(p->z, p->nAlloc);
 +    shell_check_oom(p->z);
 +  }
 +  p->z[p->n++] = (char)c;
 +}
 +
 +/* Read a single field of CSV text.  Compatible with rfc4180 and extended
 +** with the option of having a separator other than ",".
 +**
 +**   +  Input comes from p->in.
 +**   +  Store results in p->z of length p->n.  Space to hold p->z comes
 +**      from sqlite3_malloc64().
 +**   +  Use p->cSep as the column separator.  The default is ",".
 +**   +  Use p->rSep as the row separator.  The default is "\n".
 +**   +  Keep track of the line number in p->nLine.
 +**   +  Store the character that terminates the field in p->cTerm.  Store
 +**      EOF on end-of-file.
 +**   +  Report syntax errors on stderr
  */
 -#if defined(SQLITE_ENABLE_SESSION)
 -static void session_close_all(ShellState *p, int i){
 -  int j;
 -  struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i];
 -  for(j=0; j<pAuxDb->nSession; j++){
 -    session_close(&pAuxDb->aSession[j]);
 +static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
 +  int c;
-   int cSep = p->cColSep;
-   int rSep = p->cRowSep;
++  int cSep = (u8)p->cColSep;
++  int rSep = (u8)p->cRowSep;
 +  p->n = 0;
 +  c = fgetc(p->in);
 +  if( c==EOF || seenInterrupt ){
 +    p->cTerm = EOF;
 +    return 0;
    }
 -  pAuxDb->nSession = 0;
 +  if( c=='"' ){
 +    int pc, ppc;
 +    int startLine = p->nLine;
 +    int cQuote = c;
 +    pc = ppc = 0;
 +    while( 1 ){
 +      c = fgetc(p->in);
 +      if( c==rSep ) p->nLine++;
 +      if( c==cQuote ){
 +        if( pc==cQuote ){
 +          pc = 0;
 +          continue;
 +        }
 +      }
 +      if( (c==cSep && pc==cQuote)
 +       || (c==rSep && pc==cQuote)
 +       || (c==rSep && pc=='\r' && ppc==cQuote)
 +       || (c==EOF && pc==cQuote)
 +      ){
 +        do{ p->n--; }while( p->z[p->n]!=cQuote );
 +        p->cTerm = c;
 +        break;
 +      }
 +      if( pc==cQuote && c!='\r' ){
 +        utf8_printf(STD_ERR, "%s:%d: unescaped %c character\n",
 +                p->zFile, p->nLine, cQuote);
 +      }
 +      if( c==EOF ){
 +        utf8_printf(STD_ERR, "%s:%d: unterminated %c-quoted field\n",
 +                p->zFile, startLine, cQuote);
 +        p->cTerm = c;
 +        break;
 +      }
 +      import_append_char(p, c);
 +      ppc = pc;
 +      pc = c;
 +    }
 +  }else{
 +    /* If this is the first field being parsed and it begins with the
 +    ** UTF-8 BOM  (0xEF BB BF) then skip the BOM */
 +    if( (c&0xff)==0xef && p->bNotFirst==0 ){
 +      import_append_char(p, c);
 +      c = fgetc(p->in);
 +      if( (c&0xff)==0xbb ){
 +        import_append_char(p, c);
 +        c = fgetc(p->in);
 +        if( (c&0xff)==0xbf ){
 +          p->bNotFirst = 1;
 +          p->n = 0;
 +          return csv_read_one_field(p);
 +        }
 +      }
 +    }
 +    while( c!=EOF && c!=cSep && c!=rSep ){
 +      import_append_char(p, c);
 +      c = fgetc(p->in);
 +    }
 +    if( c==rSep ){
 +      p->nLine++;
 +      if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
 +    }
 +    p->cTerm = c;
 +  }
 +  if( p->z ) p->z[p->n] = 0;
 +  p->bNotFirst = 1;
 +  return p->z;
  }
 -#else
 -# define session_close_all(X,Y)
 -#endif
  
 -/*
 -** Implementation of the xFilter function for an open session.  Omit
 -** any tables named by ".session filter" but let all other table through.
 +/* Read a single field of ASCII delimited text.
 +**
 +**   +  Input comes from p->in.
 +**   +  Store results in p->z of length p->n.  Space to hold p->z comes
 +**      from sqlite3_malloc64().
 +**   +  Use p->cSep as the column separator.  The default is "\x1F".
 +**   +  Use p->rSep as the row separator.  The default is "\x1E".
 +**   +  Keep track of the row number in p->nLine.
 +**   +  Store the character that terminates the field in p->cTerm.  Store
 +**      EOF on end-of-file.
 +**   +  Report syntax errors on stderr
  */
 -#if defined(SQLITE_ENABLE_SESSION)
 -static int session_filter(void *pCtx, const char *zTab){
 -  OpenSession *pSession = (OpenSession*)pCtx;
 -  int i;
 -  for(i=0; i<pSession->nFilter; i++){
 -    if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
 +static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){
 +  int c;
-   int cSep = p->cColSep;
-   int rSep = p->cRowSep;
++  int cSep = (u8)p->cColSep;
++  int rSep = (u8)p->cRowSep;
 +  p->n = 0;
 +  c = fgetc(p->in);
 +  if( c==EOF || seenInterrupt ){
 +    p->cTerm = EOF;
 +    return 0;
    }
 -  return 1;
 +  while( c!=EOF && c!=cSep && c!=rSep ){
 +    import_append_char(p, c);
 +    c = fgetc(p->in);
 +  }
 +  if( c==rSep ){
 +    p->nLine++;
 +  }
 +  p->cTerm = c;
 +  if( p->z ) p->z[p->n] = 0;
 +  return p->z;
  }
 -#endif
  
  /*
 -** 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.
 +** Try to transfer data for table zTable.  If an error is seen while
 +** moving forward, try to go backwards.  The backwards movement won't
 +** work for WITHOUT ROWID tables.
  */
 -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 ){
 -    if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
 -       return SHELL_OPEN_ZIPFILE;
 -    }else{
 -       return SHELL_OPEN_NORMAL;
 -    }
 +static void tryToCloneData(
 +  ShellExState *psx,
 +  sqlite3 *newDb,
 +  const char *zTable
 +){
 +  sqlite3_stmt *pQuery = 0;
 +  sqlite3_stmt *pInsert = 0;
 +  char *zQuery = 0;
 +  char *zInsert = 0;
 +  int rc;
 +  int i, j, n;
 +  int nTable = strlen30(zTable);
 +  int k = 0;
 +  int cnt = 0;
 +  const int spinRate = 10000;
 +
 +  zQuery = smprintf("SELECT * FROM \"%w\"", zTable);
 +  shell_check_oom(zQuery);
 +  rc = sqlite3_prepare_v2(DBX(psx), zQuery, -1, &pQuery, 0);
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Error %d: %s on [%s]\n",
 +            sqlite3_extended_errcode(DBX(psx)), sqlite3_errmsg(DBX(psx)),
 +            zQuery);
 +    goto end_data_xfer;
    }
 -  n = fread(zBuf, 16, 1, f);
 -  if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){
 -    fclose(f);
 -    return SHELL_OPEN_NORMAL;
 +  n = sqlite3_column_count(pQuery);
 +  zInsert = sqlite3_malloc64(200 + nTable + n*3);
 +  shell_check_oom(zInsert);
 +  sqlite3_snprintf(200+nTable,zInsert,
 +                   "INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable);
 +  i = strlen30(zInsert);
 +  for(j=1; j<n; j++){
 +    memcpy(zInsert+i, ",?", 2);
 +    i += 2;
    }
 -  fseek(f, -25, SEEK_END);
 -  n = fread(zBuf, 25, 1, f);
 -  if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
 -    rc = SHELL_OPEN_APPENDVFS;
 -  }else{
 -    fseek(f, -22, SEEK_END);
 -    n = fread(zBuf, 22, 1, f);
 -    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 ){
 -      rc = SHELL_OPEN_ZIPFILE;
 -    }
 +  memcpy(zInsert+i, ");", 3);
 +  rc = sqlite3_prepare_v2(newDb, zInsert, -1, &pInsert, 0);
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Error %d: %s on [%s]\n",
 +            sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb),
 +            zQuery);
 +    goto end_data_xfer;
    }
 -  fclose(f);
 -  return rc;
 +  for(k=0; k<2; k++){
 +    while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){
 +      for(i=0; i<n; i++){
 +        switch( sqlite3_column_type(pQuery, i) ){
 +          case SQLITE_NULL: {
 +            sqlite3_bind_null(pInsert, i+1);
 +            break;
 +          }
 +          case SQLITE_INTEGER: {
 +            sqlite3_bind_int64(pInsert, i+1, sqlite3_column_int64(pQuery,i));
 +            break;
 +          }
 +          case SQLITE_FLOAT: {
 +            sqlite3_bind_double(pInsert, i+1, sqlite3_column_double(pQuery,i));
 +            break;
 +          }
 +          case SQLITE_TEXT: {
 +            sqlite3_bind_text(pInsert, i+1,
 +                             (const char*)sqlite3_column_text(pQuery,i),
 +                             -1, SQLITE_STATIC);
 +            break;
 +          }
 +          case SQLITE_BLOB: {
 +            sqlite3_bind_blob(pInsert, i+1, sqlite3_column_blob(pQuery,i),
 +                                            sqlite3_column_bytes(pQuery,i),
 +                                            SQLITE_STATIC);
 +            break;
 +          }
 +        }
 +      } /* End for */
 +      rc = sqlite3_step(pInsert);
 +      if( rc!=SQLITE_OK && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){
 +        utf8_printf(STD_ERR, "Error %d: %s\n", sqlite3_extended_errcode(newDb),
 +                        sqlite3_errmsg(newDb));
 +      }
 +      sqlite3_reset(pInsert);
 +      cnt++;
 +      if( (cnt%spinRate)==0 ){
 +        fprintf(STD_OUT, "%c\b", "|/-\\"[(cnt/spinRate)%4]);
 +        fflush(STD_OUT);
 +      }
 +    } /* End while */
 +    if( rc==SQLITE_DONE ) break;
 +    sqlite3_finalize(pQuery);
 +    sqlite3_free(zQuery);
 +    zQuery = smprintf("SELECT * FROM \"%w\" ORDER BY rowid DESC;", zTable);
 +    shell_check_oom(zQuery);
 +    rc = sqlite3_prepare_v2(DBX(psx), zQuery, -1, &pQuery, 0);
 +    if( rc ){
 +      utf8_printf(STD_ERR, "Warning: cannot step \"%s\" backwards", zTable);
 +      break;
 +    }
 +  } /* End for(k=0...) */
 +
 +end_data_xfer:
 +  sqlite3_finalize(pQuery);
 +  sqlite3_finalize(pInsert);
 +  sqlite3_free(zQuery);
 +  sqlite3_free(zInsert);
  }
  
 -#ifndef SQLITE_OMIT_DESERIALIZE
 +
  /*
 -** Reconstruct an in-memory database using the output from the "dbtotxt"
 -** program.  Read content from the file in p->aAuxDb[].zDbFilename.
 -** If p->aAuxDb[].zDbFilename is 0, then read from standard input.
 +** Try to transfer all rows of the schema that match zWhere.  For
 +** each row, invoke xForEach() on the object defined by that row.
 +** If an error is encountered while moving forward through the
 +** sqlite_schema table, try again moving backwards.
  */
 -static unsigned char *readHexDb(ShellState *p, int *pnData){
 -  unsigned char *a = 0;
 -  int nLine;
 -  int n = 0;
 -  int pgsz = 0;
 -  int iOffset = 0;
 -  int j, k;
 +static void tryToCloneSchema(
 +  ShellExState *psx,
 +  sqlite3 *newDb,
 +  const char *zWhere,
 +  void (*xForEach)(ShellExState*,sqlite3*,const char*)
 +){
 +  sqlite3_stmt *pQuery = 0;
 +  char *zQuery = 0;
    int rc;
 -  FILE *in;
 -  const char *zDbFilename = p->pAuxDb->zDbFilename;
 -  unsigned int x[16];
 -  char zLine[1000];
 -  if( zDbFilename ){
 -    in = fopen(zDbFilename, "r");
 -    if( in==0 ){
 -      utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename);
 -      return 0;
 -    }
 -    nLine = 0;
 -  }else{
 -    in = p->in;
 -    nLine = p->lineno;
 -    if( in==0 ) in = stdin;
 -  }
 -  *pnData = 0;
 -  nLine++;
 -  if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error;
 -  rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
 -  if( rc!=2 ) goto readHexDb_error;
 -  if( n<0 ) goto readHexDb_error;
 -  if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error;
 -  n = (n+pgsz-1)&~(pgsz-1);  /* Round n up to the next multiple of pgsz */
 -  a = sqlite3_malloc( n ? n : 1 );
 -  shell_check_oom(a);
 -  memset(a, 0, n);
 -  if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
 -    utf8_printf(stderr, "invalid pagesize\n");
 -    goto readHexDb_error;
 +  const unsigned char *zName;
 +  const unsigned char *zSql;
 +  char *zErrMsg = 0;
 +
-   zQuery = smprintf("SELECT name, sql FROM sqlite_schema WHERE %s", zWhere);
++  zQuery = smprintf("SELECT name, sql FROM sqlite_schema"
++                    " WHERE %s ORDER BY rowid ASC", zWhere);
 +  shell_check_oom(zQuery);
 +  rc = sqlite3_prepare_v2(DBX(psx), zQuery, -1, &pQuery, 0);
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n",
 +                sqlite3_extended_errcode(DBX(psx)),
 +                sqlite3_errmsg(DBX(psx)), zQuery);
 +    goto end_schema_xfer;
    }
 -  for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){
 -    rc = sscanf(zLine, "| page %d offset %d", &j, &k);
 -    if( rc==2 ){
 -      iOffset = k;
 -      continue;
 +  while( sqlite3_step(pQuery)==SQLITE_ROW ){
 +    zName = sqlite3_column_text(pQuery, 0);
 +    zSql = sqlite3_column_text(pQuery, 1);
 +    if( zName==0 || zSql==0 ) continue;
-     /* Consider directing this output to current output. */
-     fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT);
-     sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
-     if( zErrMsg ){
-       utf8_printf(STD_ERR, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
-       sqlite3_free(zErrMsg);
-       zErrMsg = 0;
++    if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){
++      /* Consider directing this output to current output. */
++      fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT);
++      sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
++      if( zErrMsg ){
++        utf8_printf(STD_ERR, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
++        sqlite3_free(zErrMsg);
++        zErrMsg = 0;
++      }
      }
 -    if( cli_strncmp(zLine, "| end ", 6)==0 ){
 -      break;
 +    if( xForEach ){
 +      xForEach(psx, newDb, (const char*)zName);
      }
 -    rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
 -                &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
 -                &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
 -    if( rc==17 ){
 -      k = iOffset+j;
 -      if( k+16<=n && k>=0 ){
 -        int ii;
 -        for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
 +    /* Consider directing this output to current output. */
 +    fprintf(STD_OUT, "done\n");
 +  }
 +  if( rc!=SQLITE_DONE ){
 +    sqlite3_finalize(pQuery);
 +    sqlite3_free(zQuery);
 +    zQuery = smprintf("SELECT name, sql FROM sqlite_schema"
 +                      " WHERE %s ORDER BY rowid DESC", zWhere);
 +    shell_check_oom(zQuery);
 +    rc = sqlite3_prepare_v2(DBX(psx), zQuery, -1, &pQuery, 0);
 +    if( rc ){
 +      utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n",
 +                  sqlite3_extended_errcode(DBX(psx)),
 +                  sqlite3_errmsg(DBX(psx)), zQuery);
 +      goto end_schema_xfer;
 +    }
 +    while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){
 +      zName = sqlite3_column_text(pQuery, 0);
 +      zSql = sqlite3_column_text(pQuery, 1);
 +      if( zName==0 || zSql==0 ) continue;
++      if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ) continue;
 +      /* Consider directing ... */
 +      fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT);
 +      sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
 +      if( zErrMsg ){
 +        utf8_printf(STD_ERR, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
 +        sqlite3_free(zErrMsg);
 +        zErrMsg = 0;
 +      }
 +      if( xForEach ){
 +        xForEach(psx, newDb, (const char*)zName);
        }
 +      /* Consider directing ... */
 +      fprintf(STD_OUT, "done\n");
      }
    }
 -  *pnData = n;
 -  if( in!=p->in ){
 -    fclose(in);
 +end_schema_xfer:
 +  sqlite3_finalize(pQuery);
 +  sqlite3_free(zQuery);
 +}
 +
 +/*
 +** Open a new database file named "zNewDb".  Try to recover as much information
 +** as possible out of the main database (which might be corrupt) and write it
 +** into zNewDb.
 +*/
 +static void tryToClone(ShellExState *psx, const char *zNewDb){
 +  int rc;
 +  sqlite3 *newDb = 0;
 +  if( access(zNewDb,0)==0 ){
 +    utf8_printf(STD_ERR, "File \"%s\" already exists.\n", zNewDb);
 +    return;
 +  }
 +  rc = sqlite3_open(zNewDb, &newDb);
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Cannot create output database: %s\n",
 +            sqlite3_errmsg(newDb));
    }else{
 -    p->lineno = nLine;
 +    sqlite3_exec(DBX(psx), "PRAGMA writable_schema=ON;", 0, 0, 0);
 +    sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0);
 +    tryToCloneSchema(psx, newDb, "type='table'", tryToCloneData);
 +    tryToCloneSchema(psx, newDb, "type!='table'", 0);
 +    sqlite3_exec(newDb, "COMMIT;", 0, 0, 0);
 +    sqlite3_exec(DBX(psx), "PRAGMA writable_schema=OFF;", 0, 0, 0);
    }
 -  return a;
 +  close_db(newDb);
 +}
  
 -readHexDb_error:
 -  if( in!=p->in ){
 -    fclose(in);
 +/*
 +** Change the output file back to stdout.
 +**
 +** If the psi->doXdgOpen flag is set, that means the output was being
 +** redirected to a temporary file named by psi->zTempFile.  In that case,
 +** launch start/open/xdg-open on that temporary file.
 +*/
 +static void output_reset(ShellInState *psi){
 +  if( psi->outfile[0]=='|' ){
 +#ifndef SQLITE_OMIT_POPEN
 +    pclose(psi->out);
 +#endif
    }else{
 -    while( fgets(zLine, sizeof(zLine), p->in)!=0 ){
 -      nLine++;
 -      if(cli_strncmp(zLine, "| end ", 6)==0 ) break;
 +    output_file_close(psi->out);
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +    if( psi->doXdgOpen ){
 +      const char *zXdgOpenCmd =
 +#if defined(_WIN32)
 +      "start";
 +#elif defined(__APPLE__)
 +      "open";
 +#else
 +      "xdg-open";
 +#endif
 +      char *zCmd;
 +      zCmd = smprintf("%s %s", zXdgOpenCmd, psi->zTempFile);
 +      if( system(zCmd) ){
 +        utf8_printf(STD_ERR, "Failed: [%s]\n", zCmd);
 +      }else{
 +        /* Give the start/open/xdg-open command some time to get
 +        ** going before we continue, and potential delete the
 +        ** psi->zTempFile data file out from under it */
 +        sqlite3_sleep(2000);
 +      }
 +      sqlite3_free(zCmd);
 +      outputModePop(psi);
 +      psi->doXdgOpen = 0;
      }
 -    p->lineno = nLine;
 +#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
    }
 -  sqlite3_free(a);
 -  utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine);
 -  return 0;
 +  psi->outfile[0] = 0;
 +  psi->out = STD_OUT;
  }
 -#endif /* SQLITE_OMIT_DESERIALIZE */
  
  /*
 -** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X.
 +** Run an SQL command and return the single integer result.
 +** No parameter binding is done.
  */
 -static void shellUSleepFunc(
 -  sqlite3_context *context,
 -  int argcUnused,
 -  sqlite3_value **argv
 -){
 -  int sleep = sqlite3_value_int(argv[0]);
 -  (void)argcUnused;
 -  sqlite3_sleep(sleep/1000);
 -  sqlite3_result_int(context, sleep);
 +static int db_int(sqlite3 *db, const char *zSql){
 +  sqlite3_stmt *pStmt;
 +  int res = 0;
 +  sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +  if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){
 +    res = sqlite3_column_int(pStmt,0);
 +  }
 +  sqlite3_finalize(pStmt);
 +  return res;
  }
  
 -/* Flags for open_db().
 -**
 -** The default behavior of open_db() is to exit(1) if the database fails to
 -** open.  The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
 -** but still returns without calling exit.
 -**
 -** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a
 -** ZIP archive if the file does not exist or is empty and its name matches
 -** the *.zip pattern.
 +/*
 +** Run an SQL command and return the single text result,
 +** Parameter binding is done iff bBind is true.
 +** The return must be freed by caller using sqlite3_free().
  */
 -#define OPEN_DB_KEEPALIVE   0x001   /* Return after error if true */
 -#define OPEN_DB_ZIPFILE     0x002   /* Open as ZIP if name matches *.zip */
 +static char *db_text(sqlite3 *db, const char *zSql, int bBind){
 +  sqlite3_stmt *pStmt;
 +  char *zRes = 0;
 +  sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +  if( pStmt ){
 +    if( bBind ) bind_prepared_stmt(db, pStmt);
 +    if( sqlite3_step(pStmt)==SQLITE_ROW ){
 +      zRes = smprintf("%s", sqlite3_column_text(pStmt,0));
 +      shell_check_oom(zRes);
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  return zRes;
 +}
  
 +#if SQLITE_SHELL_HAVE_RECOVER
  /*
 -** Make sure the database is open.  If it is not, then open it.  If
 -** the database fails to open, print an error message and exit.
 +** Convert a 2-byte or 4-byte big-endian integer into a native integer
  */
 -static void open_db(ShellState *p, int openFlags){
 -  if( p->db==0 ){
 -    const char *zDbFilename = p->pAuxDb->zDbFilename;
 -    if( p->openMode==SHELL_OPEN_UNSPEC ){
 -      if( zDbFilename==0 || zDbFilename[0]==0 ){
 -        p->openMode = SHELL_OPEN_NORMAL;
 -      }else{
 -        p->openMode = (u8)deduceDatabaseType(zDbFilename,
 -                             (openFlags & OPEN_DB_ZIPFILE)!=0);
 -      }
 -    }
 -    switch( p->openMode ){
 -      case SHELL_OPEN_APPENDVFS: {
 -        sqlite3_open_v2(zDbFilename, &p->db,
 -           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs");
 -        break;
 -      }
 -      case SHELL_OPEN_HEXDB:
 -      case SHELL_OPEN_DESERIALIZE: {
 -        sqlite3_open(0, &p->db);
 -        break;
 -      }
 -      case SHELL_OPEN_ZIPFILE: {
 -        sqlite3_open(":memory:", &p->db);
 -        break;
 -      }
 -      case SHELL_OPEN_READONLY: {
 -        sqlite3_open_v2(zDbFilename, &p->db,
 -            SQLITE_OPEN_READONLY|p->openFlags, 0);
 -        break;
 -      }
 -      case SHELL_OPEN_UNSPEC:
 -      case SHELL_OPEN_NORMAL: {
 -        sqlite3_open_v2(zDbFilename, &p->db,
 -           SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0);
 -        break;
 -      }
 -    }
 -    globalDb = p->db;
 -    if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
 -      utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n",
 -          zDbFilename, sqlite3_errmsg(p->db));
 -      if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
 -        exit(1);
 -      }
 -      sqlite3_close(p->db);
 -      sqlite3_open(":memory:", &p->db);
 -      if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
 -        utf8_printf(stderr,
 -          "Also: unable to open substitute in-memory database.\n"
 -        );
 -        exit(1);
 -      }else{
 -        utf8_printf(stderr,
 -          "Notice: using substitute in-memory database instead of \"%s\"\n",
 -          zDbFilename);
 -      }
 -    }
 -    sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, (int)0, (int*)0);
 +static unsigned int get2byteInt(unsigned char *a){
 +  return (a[0]<<8) + a[1];
 +}
 +static unsigned int get4byteInt(unsigned char *a){
 +  return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
 +}
  
 -    /* Reflect the use or absence of --unsafe-testing invocation. */
 -    {
 -      int testmode_on = ShellHasFlag(p,SHFLG_TestingMode);
 -      sqlite3_db_config(p->db, SQLITE_DBCONFIG_TRUSTED_SCHEMA, testmode_on,0);
 -      sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
 -    }
 +/*
 +** Implementation of the ".dbinfo" command.
 +**
 +** Return 1 on error, 2 to exit, and 0 otherwise.
 +*/
 +static int shell_dbinfo_command(ShellExState *psx, int nArg, char **azArg){
 +  static const struct { const char *zName; int ofst; } aField[] = {
 +     { "file change counter:",  24  },
 +     { "database page count:",  28  },
 +     { "freelist page count:",  36  },
 +     { "schema cookie:",        40  },
 +     { "schema format:",        44  },
 +     { "default cache size:",   48  },
 +     { "autovacuum top root:",  52  },
 +     { "incremental vacuum:",   64  },
 +     { "text encoding:",        56  },
 +     { "user version:",         60  },
 +     { "application id:",       68  },
 +     { "software version:",     96  },
 +  };
 +  static const struct { const char *zName; const char *zSql; } aQuery[] = {
 +     { "number of tables:",
 +       "SELECT count(*) FROM %s WHERE type='table'" },
 +     { "number of indexes:",
 +       "SELECT count(*) FROM %s WHERE type='index'" },
 +     { "number of triggers:",
 +       "SELECT count(*) FROM %s WHERE type='trigger'" },
 +     { "number of views:",
 +       "SELECT count(*) FROM %s WHERE type='view'" },
 +     { "schema size:",
 +       "SELECT total(length(sql)) FROM %s" },
 +  };
 +  int i, rc;
 +  unsigned iDataVersion;
 +  char *zSchemaTab;
 +  char *zDb = nArg>=2 ? azArg[1] : "main";
 +  sqlite3_stmt *pStmt = 0;
 +  unsigned char aHdr[100];
 +  FILE *out = ISS(psx)->out;
  
 -#ifndef SQLITE_OMIT_LOAD_EXTENSION
 -    sqlite3_enable_load_extension(p->db, 1);
 -#endif
 -    sqlite3_shathree_init(p->db, 0, 0);
 -    sqlite3_uint_init(p->db, 0, 0);
 -    sqlite3_decimal_init(p->db, 0, 0);
 -    sqlite3_base64_init(p->db, 0, 0);
 -    sqlite3_base85_init(p->db, 0, 0);
 -    sqlite3_regexp_init(p->db, 0, 0);
 -    sqlite3_ieee_init(p->db, 0, 0);
 -    sqlite3_series_init(p->db, 0, 0);
 -#if SQLITE_SHELL_HAVE_RECOVER
 -    sqlite3_dbdata_init(p->db, 0, 0);
 -#endif
 -#ifndef SQLITE_SHELL_FIDDLE
 -    sqlite3_fileio_init(p->db, 0, 0);
 -    sqlite3_completion_init(p->db, 0, 0);
 -#endif
 -#ifdef SQLITE_HAVE_ZLIB
 -    if( !p->bSafeModePersist ){
 -      sqlite3_zipfile_init(p->db, 0, 0);
 -      sqlite3_sqlar_init(p->db, 0, 0);
 -    }
 -#endif
 -#ifdef SQLITE_SHELL_EXTFUNCS
 -    /* Create a preprocessing mechanism for extensions to make
 -     * their own provisions for being built into the shell.
 -     * This is a short-span macro. See further below for usage.
 -     */
 -#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant
 -#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant)
 -    /* Let custom-included extensions get their ..._init() called.
 -     * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause
 -     * the extension's sqlite3_*_init( db, pzErrorMsg, pApi )
 -     * inititialization routine to be called.
 -     */
 -    {
 -      int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db);
 -    /* Let custom-included extensions expose their functionality.
 -     * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause
 -     * the SQL functions, virtual tables, collating sequences or
 -     * VFS's implemented by the extension to be registered.
 -     */
 -      if( irc==SQLITE_OK
 -          || irc==SQLITE_OK_LOAD_PERMANENTLY ){
 -        SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0);
 +  open_db(psx, 0);
 +  if( DBX(psx)==0 ) return 1;
 +  rc = sqlite3_prepare_v2(DBX(psx),
 +             "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1",
 +             -1, &pStmt, 0);
 +  if( rc ){
 +    utf8_printf(STD_ERR, "error: %s\n", sqlite3_errmsg(DBX(psx)));
 +    sqlite3_finalize(pStmt);
 +    return 1;
 +  }
 +  sqlite3_bind_text(pStmt, 1, zDb, -1, SQLITE_STATIC);
 +  if( sqlite3_step(pStmt)==SQLITE_ROW
 +   && sqlite3_column_bytes(pStmt,0)>100
 +  ){
-     memcpy(aHdr, sqlite3_column_blob(pStmt,0), 100);
++    const u8 *pb = sqlite3_column_blob(pStmt,0);
++    shell_check_oom(pb);
++    memcpy(aHdr, pb, 100);
 +    sqlite3_finalize(pStmt);
 +  }else{
 +    raw_printf(STD_ERR, "unable to read database header\n");
 +    sqlite3_finalize(pStmt);
 +    return 1;
 +  }
 +  i = get2byteInt(aHdr+16);
 +  if( i==1 ) i = 65536;
 +  utf8_printf(out, "%-20s %d\n", "database page size:", i);
 +  utf8_printf(out, "%-20s %d\n", "write format:", aHdr[18]);
 +  utf8_printf(out, "%-20s %d\n", "read format:", aHdr[19]);
 +  utf8_printf(out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
 +  for(i=0; i<ArraySize(aField); i++){
 +    int ofst = aField[i].ofst;
 +    unsigned int val = get4byteInt(aHdr + ofst);
 +    utf8_printf(out, "%-20s %u", aField[i].zName, val);
 +    switch( ofst ){
 +      case 56: {
 +        if( val==1 ) raw_printf(out, " (utf8)");
 +        if( val==2 ) raw_printf(out, " (utf16le)");
 +        if( val==3 ) raw_printf(out, " (utf16be)");
        }
 -#undef SHELL_SUB_MACRO
 -#undef SHELL_SUBMACRO
      }
 -#endif
 +    raw_printf(out, "\n");
 +  }
 +  if( zDb==0 ){
 +    zSchemaTab = smprintf("main.sqlite_schema");
 +  }else if( cli_strcmp(zDb,"temp")==0 ){
 +    zSchemaTab = smprintf("%s", "sqlite_temp_schema");
 +  }else{
 +    zSchemaTab = smprintf("\"%w\".sqlite_schema", zDb);
 +  }
 +  for(i=0; i<ArraySize(aQuery); i++){
 +    char *zSql = smprintf(aQuery[i].zSql, zSchemaTab);
 +    int val = db_int(DBX(psx), zSql);
 +    sqlite3_free(zSql);
 +    utf8_printf(out, "%-20s %d\n", aQuery[i].zName, val);
 +  }
 +  sqlite3_free(zSchemaTab);
 +  sqlite3_file_control(DBX(psx), zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
 +  utf8_printf(out, "%-20s %u\n", "data version", iDataVersion);
 +  return 0;
 +}
 +#endif /* SQLITE_SHELL_HAVE_RECOVER */
 +
 +/*
 +** Print the current sqlite3_errmsg() value to stderr and return 1.
 +*/
 +static int shellDatabaseError(sqlite3 *db){
 +  const char *zErr = sqlite3_errmsg(db);
 +  utf8_printf(STD_ERR, "Error: %s\n", zErr);
 +  return 1;
 +}
  
 -    sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
 -                            shellAddSchemaName, 0, 0);
 -    sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
 -                            shellModuleSchema, 0, 0);
 -    sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
 -                            shellPutsFunc, 0, 0);
 -    sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0,
 -                            shellUSleepFunc, 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
 +/*
 +** Compare the pattern in zGlob[] against the text in z[].  Return TRUE
 +** if they match and FALSE (0) if they do not match.
 +**
 +** Globbing rules:
 +**
 +**      '*'       Matches any sequence of zero or more characters.
 +**
 +**      '?'       Matches exactly one character.
 +**
 +**     [...]      Matches one character from the enclosed list of
 +**                characters.
 +**
 +**     [^...]     Matches one character not in the enclosed list.
 +**
 +**      '#'       Matches any sequence of one or more digits with an
 +**                optional + or - sign in front
 +**
 +**      ' '       Any span of whitespace matches any other span of
 +**                whitespace.
 +**
 +** Extra whitespace at the end of z[] is ignored.
 +*/
 +static int testcase_glob(const char *zGlob, const char *z){
 +  int c, c2;
 +  int invert;
 +  int seen;
  
 -    if( p->openMode==SHELL_OPEN_ZIPFILE ){
 -      char *zSql = sqlite3_mprintf(
 -         "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename);
 -      shell_check_oom(zSql);
 -      sqlite3_exec(p->db, zSql, 0, 0, 0);
 -      sqlite3_free(zSql);
 -    }
 -#ifndef SQLITE_OMIT_DESERIALIZE
 -    else
 -    if( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){
 -      int rc;
 -      int nData = 0;
 -      unsigned char *aData;
 -      if( p->openMode==SHELL_OPEN_DESERIALIZE ){
 -        aData = (unsigned char*)readFile(zDbFilename, &nData);
 -      }else{
 -        aData = readHexDb(p, &nData);
 +  while( (c = (*(zGlob++)))!=0 ){
 +    if( IsSpace(c) ){
 +      if( !IsSpace(*z) ) return 0;
 +      zGlob = skipWhite(zGlob);
 +      z = skipWhite(z);
 +    }else if( c=='*' ){
 +      while( (c=(*(zGlob++))) == '*' || c=='?' ){
 +        if( c=='?' && (*(z++))==0 ) return 0;
        }
 -      if( aData==0 ){
 -        return;
 +      if( c==0 ){
 +        return 1;
 +      }else if( c=='[' ){
 +        while( *z && testcase_glob(zGlob-1,z)==0 ){
 +          z++;
 +        }
 +        return (*z)!=0;
        }
 -      rc = sqlite3_deserialize(p->db, "main", aData, nData, nData,
 -                   SQLITE_DESERIALIZE_RESIZEABLE |
 -                   SQLITE_DESERIALIZE_FREEONCLOSE);
 -      if( rc ){
 -        utf8_printf(stderr, "Error: sqlite3_deserialize() returns %d\n", rc);
 +      while( (c2 = (*(z++)))!=0 ){
 +        while( c2!=c ){
 +          c2 = *(z++);
 +          if( c2==0 ) return 0;
 +        }
 +        if( testcase_glob(zGlob,z) ) return 1;
 +      }
 +      return 0;
 +    }else if( c=='?' ){
 +      if( (*(z++))==0 ) return 0;
 +    }else if( c=='[' ){
 +      int prior_c = 0;
 +      seen = 0;
 +      invert = 0;
 +      c = *(z++);
 +      if( c==0 ) return 0;
 +      c2 = *(zGlob++);
 +      if( c2=='^' ){
 +        invert = 1;
 +        c2 = *(zGlob++);
        }
 -      if( p->szMax>0 ){
 -        sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax);
 +      if( c2==']' ){
 +        if( c==']' ) seen = 1;
 +        c2 = *(zGlob++);
        }
 +      while( c2 && c2!=']' ){
 +        if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
 +          c2 = *(zGlob++);
 +          if( c>=prior_c && c<=c2 ) seen = 1;
 +          prior_c = 0;
 +        }else{
 +          if( c==c2 ){
 +            seen = 1;
 +          }
 +          prior_c = c2;
 +        }
 +        c2 = *(zGlob++);
 +      }
 +      if( c2==0 || (seen ^ invert)==0 ) return 0;
 +    }else if( c=='#' ){
 +      if( (z[0]=='-' || z[0]=='+') && IsDigit(z[1]) ) z++;
 +      if( !IsDigit(z[0]) ) return 0;
 +      z++;
 +      while( IsDigit(z[0]) ){ z++; }
 +    }else{
 +      if( c!=(*(z++)) ) return 0;
      }
 -#endif
 -  }
 -  if( p->db!=0 ){
 -    if( p->bSafeModePersist ){
 -      sqlite3_set_authorizer(p->db, safeModeAuth, p);
 -    }
 -    sqlite3_db_config(
 -        p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0
 -    );
    }
 +  return *skipWhite(z)==0;
  }
  
  /*
 -** Attempt to close the databaes connection.  Report errors.
 +** Compare the string as a command-line option with either one or two
 +** initial "-" characters.
  */
 -void close_db(sqlite3 *db){
 -  int rc = sqlite3_close(db);
 -  if( rc ){
 -    utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n",
 -        rc, sqlite3_errmsg(db));
 -  }
 +static int optionMatch(const char *zStr, const char *zOpt){
 +  if( zStr[0]!='-' ) return 0;
 +  zStr++;
 +  if( zStr[0]=='-' ) zStr++;
 +  return cli_strcmp(zStr, zOpt)==0;
  }
  
 -#if HAVE_READLINE || HAVE_EDITLINE
  /*
 -** Readline completion callbacks
 +** Delete a file.
  */
 -static char *readline_completion_generator(const char *text, int state){
 -  static sqlite3_stmt *pStmt = 0;
 -  char *zRet;
 -  if( state==0 ){
 -    char *zSql;
 -    sqlite3_finalize(pStmt);
 -    zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
 -                           "  FROM completion(%Q) ORDER BY 1", text);
 -    shell_check_oom(zSql);
 -    sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
 -    sqlite3_free(zSql);
 -  }
 -  if( sqlite3_step(pStmt)==SQLITE_ROW ){
 -    const char *z = (const char*)sqlite3_column_text(pStmt,0);
 -    zRet = z ? strdup(z) : 0;
 +int shellDeleteFile(const char *zFilename){
 +  int rc;
 +#ifdef _WIN32
 +  wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename);
 +  rc = _wunlink(z);
 +  sqlite3_free(z);
 +#else
 +  rc = unlink(zFilename);
 +#endif
 +  return rc;
 +}
 +
 +/*
 +** Try to delete the temporary file (if there is one) and free the
 +** memory used to hold the name of the temp file.
 +*/
 +static void clearTempFile(ShellInState *psi){
 +  if( psi->zTempFile==0 ) return;
 +  if( psi->doXdgOpen ) return;
 +  if( shellDeleteFile(psi->zTempFile) ) return;
 +  sqlite3_free(psi->zTempFile);
 +  psi->zTempFile = 0;
 +}
 +
 +/*
 +** Create a new temp file name with the given suffix.
 +*/
 +static void newTempFile(ShellInState *psi, const char *zSuffix){
 +  clearTempFile(psi);
 +  sqlite3_free(psi->zTempFile);
 +  psi->zTempFile = 0;
 +  if( DBI(psi) ){
 +    sqlite3_file_control(DBI(psi), 0, SQLITE_FCNTL_TEMPFILENAME,
 +                         &psi->zTempFile);
 +  }
 +  if( psi->zTempFile==0 ){
 +    /* If DB is an in-memory database then the TEMPFILENAME file-control
 +    ** will not work and we will need to fallback to guessing */
 +    char *zTemp;
 +    sqlite3_uint64 r;
 +    sqlite3_randomness(sizeof(r), &r);
 +    zTemp = getenv("TEMP");
 +    if( zTemp==0 ) zTemp = getenv("TMP");
 +    if( zTemp==0 ){
 +#ifdef _WIN32
 +      zTemp = "\\tmp";
 +#else
 +      zTemp = "/tmp";
 +#endif
 +    }
 +    psi->zTempFile = smprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
    }else{
 -    sqlite3_finalize(pStmt);
 -    pStmt = 0;
 -    zRet = 0;
 +    psi->zTempFile = smprintf("%z.%s", psi->zTempFile, zSuffix);
    }
 -  return zRet;
 -}
 -static char **readline_completion(const char *zText, int iStart, int iEnd){
 -  (void)iStart;
 -  (void)iEnd;
 -  rl_attempted_completion_over = 1;
 -  return rl_completion_matches(zText, readline_completion_generator);
 +  shell_check_oom(psi->zTempFile);
  }
  
 -#elif HAVE_LINENOISE
  /*
 -** Linenoise completion callback
 +** The implementation of SQL scalar function fkey_collate_clause(), used
 +** by the ".lint fkey-indexes" command. This scalar function is always
 +** called with four arguments - the parent table name, the parent column name,
 +** the child table name and the child column name.
 +**
 +**   fkey_collate_clause('parent-tab', 'parent-col', 'child-tab', 'child-col')
 +**
 +** If either of the named tables or columns do not exist, this function
 +** returns an empty string. An empty string is also returned if both tables
 +** and columns exist but have the same default collation sequence. Or,
 +** if both exist but the default collation sequences are different, this
 +** function returns the string " COLLATE <parent-collation>", where
 +** <parent-collation> is the default collation sequence of the parent column.
  */
 -static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
 -  i64 nLine = strlen(zLine);
 -  i64 i, iStart;
 -  sqlite3_stmt *pStmt = 0;
 -  char *zSql;
 -  char zBuf[1000];
 +static void shellFkeyCollateClause(
 +  sqlite3_context *pCtx,
 +  int nVal,
 +  sqlite3_value **apVal
 +){
 +  sqlite3 *db = sqlite3_context_db_handle(pCtx);
 +  const char *zParent;
 +  const char *zParentCol;
 +  const char *zParentSeq;
 +  const char *zChild;
 +  const char *zChildCol;
 +  const char *zChildSeq = 0;  /* Initialize to avoid false-positive warning */
 +  int rc;
  
 -  if( nLine>(i64)sizeof(zBuf)-30 ) return;
 -  if( zLine[0]=='.' || zLine[0]=='#') return;
 -  for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
 -  if( i==nLine-1 ) return;
 -  iStart = i+1;
 -  memcpy(zBuf, zLine, iStart);
 -  zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
 -                         "  FROM completion(%Q,%Q) ORDER BY 1",
 -                         &zLine[iStart], zLine);
 -  shell_check_oom(zSql);
 -  sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
 -  sqlite3_free(zSql);
 -  sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
 -  while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -    const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
 -    int nCompletion = sqlite3_column_bytes(pStmt, 0);
 -    if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){
 -      memcpy(zBuf+iStart, zCompletion, nCompletion+1);
 -      linenoiseAddCompletion(lc, zBuf);
 +  assert( nVal==4 );
 +  zParent = (const char*)sqlite3_value_text(apVal[0]);
 +  zParentCol = (const char*)sqlite3_value_text(apVal[1]);
 +  zChild = (const char*)sqlite3_value_text(apVal[2]);
 +  zChildCol = (const char*)sqlite3_value_text(apVal[3]);
 +
 +  sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC);
 +  rc = sqlite3_table_column_metadata(
 +      db, "main", zParent, zParentCol, 0, &zParentSeq, 0, 0, 0
 +  );
 +  if( rc==SQLITE_OK ){
 +    rc = sqlite3_table_column_metadata(
 +        db, "main", zChild, zChildCol, 0, &zChildSeq, 0, 0, 0
 +    );
 +  }
 +
 +  if( rc==SQLITE_OK && sqlite3_stricmp(zParentSeq, zChildSeq) ){
 +    char *z = smprintf(" COLLATE %s", zParentSeq);
 +    sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
 +    sqlite3_free(z);
 +  }
 +}
 +
 +#if !defined SQLITE_OMIT_VIRTUALTABLE
 +static void shellPrepare(
 +  sqlite3 *db,
 +  int *pRc,
 +  const char *zSql,
 +  sqlite3_stmt **ppStmt
 +){
 +  *ppStmt = 0;
 +  if( *pRc==SQLITE_OK ){
 +    int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
 +    if( rc!=SQLITE_OK ){
 +      raw_printf(STD_ERR, "sql error: %s (%d)\n",
 +          sqlite3_errmsg(db), sqlite3_errcode(db)
 +      );
 +      *pRc = rc;
      }
    }
 -  sqlite3_finalize(pStmt);
  }
 -#endif
  
  /*
 -** Do C-language style dequoting.
 +** Create a prepared statement using printf-style arguments for the SQL.
  **
 -**    \a    -> alarm
 -**    \b    -> backspace
 -**    \t    -> tab
 -**    \n    -> newline
 -**    \v    -> vertical tab
 -**    \f    -> form feed
 -**    \r    -> carriage return
 -**    \s    -> space
 -**    \"    -> "
 -**    \'    -> '
 -**    \\    -> backslash
 -**    \NNN  -> ascii character NNN in octal
 -**    \xHH  -> ascii character HH in hexadecimal
 +** This routine is could be marked "static".  But it is not always used,
 +** depending on compile-time options.  By omitting the "static", we avoid
 +** nuisance compiler warnings about "defined but not used".
  */
 -static void resolve_backslashes(char *z){
 -  int i, j;
 -  char c;
 -  while( *z && *z!='\\' ) z++;
 -  for(i=j=0; (c = z[i])!=0; i++, j++){
 -    if( c=='\\' && z[i+1]!=0 ){
 -      c = z[++i];
 -      if( c=='a' ){
 -        c = '\a';
 -      }else if( c=='b' ){
 -        c = '\b';
 -      }else if( c=='t' ){
 -        c = '\t';
 -      }else if( c=='n' ){
 -        c = '\n';
 -      }else if( c=='v' ){
 -        c = '\v';
 -      }else if( c=='f' ){
 -        c = '\f';
 -      }else if( c=='r' ){
 -        c = '\r';
 -      }else if( c=='"' ){
 -        c = '"';
 -      }else if( c=='\'' ){
 -        c = '\'';
 -      }else if( c=='\\' ){
 -        c = '\\';
 -      }else if( c=='x' ){
 -        int nhd = 0, hdv;
 -        u8 hv = 0;
 -        while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){
 -          hv = (u8)((hv<<4)|hdv);
 -          ++nhd;
 -        }
 -        i += nhd;
 -        c = (u8)hv;
 -      }else if( c>='0' && c<='7' ){
 -        c -= '0';
 -        if( z[i+1]>='0' && z[i+1]<='7' ){
 -          i++;
 -          c = (c<<3) + z[i] - '0';
 -          if( z[i+1]>='0' && z[i+1]<='7' ){
 -            i++;
 -            c = (c<<3) + z[i] - '0';
 -          }
 -        }
 -      }
 +void shellPreparePrintf(
 +  sqlite3 *db,
 +  int *pRc,
 +  sqlite3_stmt **ppStmt,
 +  const char *zFmt,
 +  ...
 +){
 +  *ppStmt = 0;
 +  if( *pRc==SQLITE_OK ){
 +    va_list ap;
 +    char *z;
 +    va_start(ap, zFmt);
 +    z = sqlite3_vmprintf(zFmt, ap);
 +    va_end(ap);
 +    if( z==0 ){
 +      *pRc = SQLITE_NOMEM;
 +    }else{
 +      shellPrepare(db, pRc, z, ppStmt);
 +      sqlite3_free(z);
      }
 -    z[j] = c;
    }
 -  if( j<i ) z[j] = 0;
  }
  
 -/*
 -** Interpret zArg as either an integer or a boolean value.  Return 1 or 0
 -** for TRUE and FALSE.  Return the integer value if appropriate.
 +/* Finalize the prepared statement created using shellPreparePrintf().
 +**
 +** This routine is could be marked "static".  But it is not always used,
 +** depending on compile-time options.  By omitting the "static", we avoid
 +** nuisance compiler warnings about "defined but not used".
  */
 -static int booleanValue(const char *zArg){
 -  int i;
 -  if( zArg[0]=='0' && zArg[1]=='x' ){
 -    for(i=2; hexDigitValue(zArg[i])>=0; i++){}
 -  }else{
 -    for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
 -  }
 -  if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
 -  if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){
 -    return 1;
 -  }
 -  if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
 -    return 0;
 +void shellFinalize(
 +  int *pRc,
 +  sqlite3_stmt *pStmt
 +){
 +  if( pStmt ){
 +    sqlite3 *db = sqlite3_db_handle(pStmt);
 +    int rc = sqlite3_finalize(pStmt);
 +    if( *pRc==SQLITE_OK ){
 +      if( rc!=SQLITE_OK ){
 +        raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db));
 +      }
 +      *pRc = rc;
 +    }
    }
 -  utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
 -          zArg);
 -  return 0;
  }
  
 -/*
 -** Set or clear a shell flag according to a boolean value.
 +/* Reset the prepared statement created using shellPreparePrintf().
 +**
 +** This routine is could be marked "static".  But it is not always used,
 +** depending on compile-time options.  By omitting the "static", we avoid
 +** nuisance compiler warnings about "defined but not used".
  */
 -static void setOrClearFlag(ShellState *p, unsigned mFlag, const char *zArg){
 -  if( booleanValue(zArg) ){
 -    ShellSetFlag(p, mFlag);
 -  }else{
 -    ShellClearFlag(p, mFlag);
 +void shellReset(
 +  int *pRc, 
 +  sqlite3_stmt *pStmt
 +){
 +  int rc = sqlite3_reset(pStmt);
 +  if( *pRc==SQLITE_OK ){
 +    if( rc!=SQLITE_OK ){
 +      sqlite3 *db = sqlite3_db_handle(pStmt);
 +      raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db));
 +    }
 +    *pRc = rc;
    }
  }
 +#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */
  
 +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) \
 +  && !defined(SQLITE_SHELL_FIDDLE)
 +/******************************************************************************
 +** The ".archive" or ".ar" command.
 +*/
  /*
 -** Close an output file, assuming it is not stderr or stdout
 +** Structure representing a single ".ar" command.
  */
 -static void output_file_close(FILE *f){
 -  if( f && f!=stdout && f!=stderr ) fclose(f);
 -}
 +typedef struct ArCommand ArCommand;
 +struct ArCommand {
 +  u8 eCmd;                        /* An AR_CMD_* value */
 +  u8 bVerbose;                    /* True if --verbose */
 +  u8 bZip;                        /* True if the archive is a ZIP */
 +  u8 bDryRun;                     /* True if --dry-run */
 +  u8 bAppend;                     /* True if --append */
 +  u8 bGlob;                       /* True if --glob */
 +  u8 fromCmdLine;                 /* Run from -A instead of .archive */
 +  int nArg;                       /* Number of command arguments */
 +  char *zSrcTable;                /* "sqlar", "zipfile($file)" or "zip" */
 +  const char *zFile;              /* --file argument, or NULL */
 +  const char *zDir;               /* --directory argument, or NULL */
 +  char **azArg;                   /* Array of command arguments */
 +  ShellExState *p;                /* Shell state */
 +  FILE *out;                      /* Where to put normal messages or info */
 +  sqlite3 *db;                    /* Database containing the archive */
 +};
  
  /*
 -** Try to open an output file.   The names "stdout" and "stderr" are
 -** recognized and do the right thing.  NULL is returned if the output
 -** filename is "off".
 +** Print a usage message for the .ar command to stderr and return SQLITE_ERROR.
  */
 -static FILE *output_file_open(const char *zFile, int bTextMode){
 -  FILE *f;
 -  if( cli_strcmp(zFile,"stdout")==0 ){
 -    f = stdout;
 -  }else if( cli_strcmp(zFile, "stderr")==0 ){
 -    f = stderr;
 -  }else if( cli_strcmp(zFile, "off")==0 ){
 -    f = 0;
 -  }else{
 -    f = fopen(zFile, bTextMode ? "w" : "wb");
 -    if( f==0 ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
 -    }
 -  }
 -  return f;
 +static int arUsage(FILE *f, ArCommand *pAr){
 +  showHelp(f,"archive", pAr->p);
 +  return SQLITE_ERROR;
  }
  
 -#ifndef SQLITE_OMIT_TRACE
  /*
 -** A routine for handling output from sqlite3_trace().
 +** Print an error message for the .ar command to stderr and return
 +** SQLITE_ERROR.
  */
 -static int sql_trace_callback(
 -  unsigned mType,         /* The trace type */
 -  void *pArg,             /* The ShellState pointer */
 -  void *pP,               /* Usually a pointer to sqlite_stmt */
 -  void *pX                /* Auxiliary output */
 -){
 -  ShellState *p = (ShellState*)pArg;
 -  sqlite3_stmt *pStmt;
 -  const char *zSql;
 -  i64 nSql;
 -  if( p->traceOut==0 ) return 0;
 -  if( mType==SQLITE_TRACE_CLOSE ){
 -    utf8_printf(p->traceOut, "-- closing database connection\n");
 -    return 0;
 -  }
 -  if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){
 -    zSql = (const char*)pX;
 +static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){
 +  va_list ap;
 +  char *z;
 +  va_start(ap, zFmt);
 +  z = sqlite3_vmprintf(zFmt, ap);
 +  va_end(ap);
 +  utf8_printf(STD_ERR, "Error: %s\n", z);
 +  if( pAr->fromCmdLine ){
 +    utf8_printf(STD_ERR, "Use \"-A\" for more help\n");
    }else{
 -    pStmt = (sqlite3_stmt*)pP;
 -    switch( p->eTraceType ){
 -      case SHELL_TRACE_EXPANDED: {
 -        zSql = sqlite3_expanded_sql(pStmt);
 -        break;
 -      }
 -#ifdef SQLITE_ENABLE_NORMALIZE
 -      case SHELL_TRACE_NORMALIZED: {
 -        zSql = sqlite3_normalized_sql(pStmt);
 -        break;
 -      }
 -#endif
 -      default: {
 -        zSql = sqlite3_sql(pStmt);
 -        break;
 -      }
 -    }
 -  }
 -  if( zSql==0 ) return 0;
 -  nSql = strlen(zSql);
 -  if( nSql>1000000000 ) nSql = 1000000000;
 -  while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; }
 -  switch( mType ){
 -    case SQLITE_TRACE_ROW:
 -    case SQLITE_TRACE_STMT: {
 -      utf8_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql);
 -      break;
 -    }
 -    case SQLITE_TRACE_PROFILE: {
 -      sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0;
 -      utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec);
 -      break;
 -    }
 +    utf8_printf(STD_ERR, "Use \".archive --help\" for more help\n");
    }
 -  return 0;
 +  sqlite3_free(z);
 +  return SQLITE_ERROR;
  }
 -#endif
  
  /*
 -** A no-op routine that runs with the ".breakpoint" doc-command.  This is
 -** a useful spot to set a debugger breakpoint.
 -**
 -** This routine does not do anything practical.  The code are there simply
 -** to prevent the compiler from optimizing this routine out.
 +** Values for ArCommand.eCmd.
 +*/
 +#define AR_CMD_CREATE       1
 +#define AR_CMD_UPDATE       2
 +#define AR_CMD_INSERT       3
 +#define AR_CMD_EXTRACT      4
 +#define AR_CMD_LIST         5
 +#define AR_CMD_HELP         6
 +#define AR_CMD_REMOVE       7
 +
 +/*
 +** Other (non-command) switches.
  */
 -static void test_breakpoint(void){
 -  static unsigned int nCall = 0;
 -  if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n");
 -}
 +#define AR_SWITCH_VERBOSE     8
 +#define AR_SWITCH_FILE        9
 +#define AR_SWITCH_DIRECTORY  10
 +#define AR_SWITCH_APPEND     11
 +#define AR_SWITCH_DRYRUN     12
 +#define AR_SWITCH_GLOB       13
  
 -/*
 -** An object used to read a CSV and other files for import.
 -*/
 -typedef struct ImportCtx ImportCtx;
 -struct ImportCtx {
 -  const char *zFile;  /* Name of the input file */
 -  FILE *in;           /* Read the CSV text from this input stream */
 -  int (SQLITE_CDECL *xCloser)(FILE*);      /* Func to close in */
 -  char *z;            /* Accumulated text for a field */
 -  int n;              /* Number of bytes in z */
 -  int nAlloc;         /* Space allocated for z[] */
 -  int nLine;          /* Current line number */
 -  int nRow;           /* Number of rows imported */
 -  int nErr;           /* Number of errors encountered */
 -  int bNotFirst;      /* True if one or more bytes already read */
 -  int cTerm;          /* Character that terminated the most recent field */
 -  int cColSep;        /* The column separator character.  (Usually ",") */
 -  int cRowSep;        /* The row separator character.  (Usually "\n") */
 -};
 +static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){
 +  switch( eSwitch ){
 +    case AR_CMD_CREATE:
 +    case AR_CMD_EXTRACT:
 +    case AR_CMD_LIST:
 +    case AR_CMD_REMOVE:
 +    case AR_CMD_UPDATE:
 +    case AR_CMD_INSERT:
 +    case AR_CMD_HELP:
 +      if( pAr->eCmd ){
 +        return arErrorMsg(pAr, "multiple command options");
 +      }
 +      pAr->eCmd = eSwitch;
 +      break;
  
 -/* Clean up resourced used by an ImportCtx */
 -static void import_cleanup(ImportCtx *p){
 -  if( p->in!=0 && p->xCloser!=0 ){
 -    p->xCloser(p->in);
 -    p->in = 0;
 +    case AR_SWITCH_DRYRUN:
 +      pAr->bDryRun = 1;
 +      break;
 +    case AR_SWITCH_GLOB:
 +      pAr->bGlob = 1;
 +      break;
 +    case AR_SWITCH_VERBOSE:
 +      pAr->bVerbose = 1;
 +      break;
 +    case AR_SWITCH_APPEND:
 +      pAr->bAppend = 1;
-       /* Fall thru into --file */
++      deliberate_fall_through;
 +    case AR_SWITCH_FILE:
 +      pAr->zFile = zArg;
 +      break;
 +    case AR_SWITCH_DIRECTORY:
 +      pAr->zDir = zArg;
 +      break;
    }
 -  sqlite3_free(p->z);
 -  p->z = 0;
 -}
  
 -/* Append a single byte to z[] */
 -static void import_append_char(ImportCtx *p, int c){
 -  if( p->n+1>=p->nAlloc ){
 -    p->nAlloc += p->nAlloc + 100;
 -    p->z = sqlite3_realloc64(p->z, p->nAlloc);
 -    shell_check_oom(p->z);
 -  }
 -  p->z[p->n++] = (char)c;
 +  return SQLITE_OK;
  }
  
 -/* Read a single field of CSV text.  Compatible with rfc4180 and extended
 -** with the option of having a separator other than ",".
 -**
 -**   +  Input comes from p->in.
 -**   +  Store results in p->z of length p->n.  Space to hold p->z comes
 -**      from sqlite3_malloc64().
 -**   +  Use p->cSep as the column separator.  The default is ",".
 -**   +  Use p->rSep as the row separator.  The default is "\n".
 -**   +  Keep track of the line number in p->nLine.
 -**   +  Store the character that terminates the field in p->cTerm.  Store
 -**      EOF on end-of-file.
 -**   +  Report syntax errors on stderr
 +/*
 +** Parse the command line for an ".ar" command. The results are written into
 +** structure (*pAr). DCR_Ok is returned if the command line is parsed
 +** successfully, otherwise an error message is written to stderr and an
 +** appropriate DCR_* argument error is returned.
  */
 -static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
 -  int c;
 -  int cSep = (u8)p->cColSep;
 -  int rSep = (u8)p->cRowSep;
 -  p->n = 0;
 -  c = fgetc(p->in);
 -  if( c==EOF || seenInterrupt ){
 -    p->cTerm = EOF;
 -    return 0;
 -  }
 -  if( c=='"' ){
 -    int pc, ppc;
 -    int startLine = p->nLine;
 -    int cQuote = c;
 -    pc = ppc = 0;
 -    while( 1 ){
 -      c = fgetc(p->in);
 -      if( c==rSep ) p->nLine++;
 -      if( c==cQuote ){
 -        if( pc==cQuote ){
 -          pc = 0;
 -          continue;
 +static DotCmdRC arParseCommand(
 +  char **azArg,                   /* Array of arguments passed to dot command */
 +  int nArg,                       /* Number of entries in azArg[] */
 +  ArCommand *pAr                  /* Populate this object */
 +){
 +  struct ArSwitch {
 +    const char *zLong;
 +    char cShort;
 +    u8 eSwitch;
 +    u8 bArg;
 +  } aSwitch[] = {
 +    { "create",    'c', AR_CMD_CREATE,       0 },
 +    { "extract",   'x', AR_CMD_EXTRACT,      0 },
 +    { "insert",    'i', AR_CMD_INSERT,       0 },
 +    { "list",      't', AR_CMD_LIST,         0 },
 +    { "remove",    'r', AR_CMD_REMOVE,       0 },
 +    { "update",    'u', AR_CMD_UPDATE,       0 },
 +    { "help",      'h', AR_CMD_HELP,         0 },
 +    { "verbose",   'v', AR_SWITCH_VERBOSE,   0 },
 +    { "file",      'f', AR_SWITCH_FILE,      1 },
 +    { "append",    'a', AR_SWITCH_APPEND,    1 },
 +    { "directory", 'C', AR_SWITCH_DIRECTORY, 1 },
 +    { "dryrun",    'n', AR_SWITCH_DRYRUN,    0 },
 +    { "glob",      'g', AR_SWITCH_GLOB,      0 },
 +  };
 +  int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch);
 +  struct ArSwitch *pEnd = &aSwitch[nSwitch];
 +  DotCmdRC rv = DCR_Ok;
 +
 +  if( nArg<=1 ){
 +    utf8_printf(STD_ERR, "Error: Wrong number of arguments to \"%s\".\n",
 +                azArg[0]);
 +    if( stdin_is_interactive ){
 +      utf8_printf(STD_ERR, "Usage:\n");
 +      arUsage(STD_ERR, pAr);
 +      return DCR_CmdErred;
 +    }
 +  }else{
 +    char *z = azArg[1];
 +    if( z[0]!='-' ){
 +      /* Traditional style [tar] invocation */
 +      int i;
 +      int iArg = 2;
 +      for(i=0; z[i]; i++){
 +        const char *zArg = 0;
 +        struct ArSwitch *pOpt;
 +        for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 +          if( z[i]==pOpt->cShort ) break;
          }
 +        if( pOpt==pEnd ){
 +          arErrorMsg(pAr, "unrecognized option: %c", z[i]);
 +          return DCR_Unknown|i;
 +        }
 +        if( pOpt->bArg ){
 +          if( iArg>=nArg ){
 +            arErrorMsg(pAr, "option requires an argument: %c",z[i]);
 +            return DCR_Unpaired|i;
 +          }
 +          zArg = azArg[iArg++];
 +        }
 +        rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg);
 +        if( rv!=DCR_Ok ) return rv;
        }
 -      if( (c==cSep && pc==cQuote)
 -       || (c==rSep && pc==cQuote)
 -       || (c==rSep && pc=='\r' && ppc==cQuote)
 -       || (c==EOF && pc==cQuote)
 -      ){
 -        do{ p->n--; }while( p->z[p->n]!=cQuote );
 -        p->cTerm = c;
 -        break;
 -      }
 -      if( pc==cQuote && c!='\r' ){
 -        utf8_printf(stderr, "%s:%d: unescaped %c character\n",
 -                p->zFile, p->nLine, cQuote);
 -      }
 -      if( c==EOF ){
 -        utf8_printf(stderr, "%s:%d: unterminated %c-quoted field\n",
 -                p->zFile, startLine, cQuote);
 -        p->cTerm = c;
 -        break;
 +      pAr->nArg = nArg-iArg;
 +      if( pAr->nArg>0 ){
 +        pAr->azArg = &azArg[iArg];
        }
 -      import_append_char(p, c);
 -      ppc = pc;
 -      pc = c;
 -    }
 -  }else{
 -    /* If this is the first field being parsed and it begins with the
 -    ** UTF-8 BOM  (0xEF BB BF) then skip the BOM */
 -    if( (c&0xff)==0xef && p->bNotFirst==0 ){
 -      import_append_char(p, c);
 -      c = fgetc(p->in);
 -      if( (c&0xff)==0xbb ){
 -        import_append_char(p, c);
 -        c = fgetc(p->in);
 -        if( (c&0xff)==0xbf ){
 -          p->bNotFirst = 1;
 -          p->n = 0;
 -          return csv_read_one_field(p);
 +    }else{
 +      /* Non-traditional invocation */
 +      int iArg;
 +      for(iArg=1; iArg<nArg; iArg++){
 +        int n;
 +        z = azArg[iArg];
 +        if( z[0]!='-' ){
 +          /* All remaining command line words are command arguments. */
 +          pAr->azArg = &azArg[iArg];
 +          pAr->nArg = nArg-iArg;
 +          break;
 +        }
 +        n = strlen30(z);
 +
 +        if( z[1]!='-' ){
 +          int i;
 +          /* One or more short options */
 +          for(i=1; i<n; i++){
 +            const char *zArg = 0;
 +            struct ArSwitch *pOpt;
 +            for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 +              if( z[i]==pOpt->cShort ) break;
 +            }
 +            if( pOpt==pEnd ){
 +              arErrorMsg(pAr, "unrecognized option: %c", z[i]);
 +              return DCR_Unknown|iArg;
 +            }
 +            if( pOpt->bArg ){
 +              if( i<(n-1) ){
 +                zArg = &z[i+1];
 +                i = n;
 +              }else{
 +                if( iArg>=(nArg-1) ){
 +                  arErrorMsg(pAr, "option requires an argument: %c", z[i]);
 +                  return DCR_Unpaired|iArg;
 +                }
 +                zArg = azArg[++iArg];
 +              }
 +            }
 +            rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg);
 +            if( rv!=DCR_Ok ) return rv;
 +          }
 +        }else if( z[2]=='\0' ){
 +          /* A -- option, indicating that all remaining command line words
 +          ** are command arguments.  */
 +          pAr->azArg = &azArg[iArg+1];
 +          pAr->nArg = nArg-iArg-1;
 +          break;
 +        }else{
 +          /* A long option */
 +          const char *zArg = 0;             /* Argument for option, if any */
 +          struct ArSwitch *pMatch = 0;      /* Matching option */
 +          struct ArSwitch *pOpt;            /* Iterator */
 +          for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
 +            const char *zLong = pOpt->zLong;
 +            if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){
 +              if( pMatch ){
 +                arErrorMsg(pAr, "ambiguous option: %s",z);
 +                return DCR_Ambiguous|iArg;
 +              }else{
 +                pMatch = pOpt;
 +              }
 +            }
 +          }
 +
 +          if( pMatch==0 ){
 +            arErrorMsg(pAr, "unrecognized option: %s", z);
 +            return DCR_Unknown|iArg;
 +          }
 +          if( pMatch->bArg ){
 +            if( iArg>=(nArg-1) ){
 +              arErrorMsg(pAr, "option requires an argument: %s", z);
 +              return DCR_Unpaired|iArg;
 +            }
 +            zArg = azArg[++iArg];
 +          }
 +          if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR;
          }
        }
      }
 -    while( c!=EOF && c!=cSep && c!=rSep ){
 -      import_append_char(p, c);
 -      c = fgetc(p->in);
 -    }
 -    if( c==rSep ){
 -      p->nLine++;
 -      if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
 -    }
 -    p->cTerm = c;
    }
 -  if( p->z ) p->z[p->n] = 0;
 -  p->bNotFirst = 1;
 -  return p->z;
++  if( pAr->eCmd==0 ){
++    utf8_printf(stderr, "Required argument missing.  Usage:\n");
++    return arUsage(stderr);
++  }
 +  return SQLITE_OK;
  }
  
 -/* Read a single field of ASCII delimited text.
 +/*
 +** This function assumes that all arguments within the ArCommand.azArg[]
 +** array refer to archive members, as for the --extract, --list or --remove
 +** commands. It checks that each of them are "present". If any specified
 +** file is not present in the archive, an error is printed to stderr and an
 +** error code returned. Otherwise, if all specified arguments are present
 +** in the archive, SQLITE_OK is returned. Here, "present" means either an
 +** exact equality when pAr->bGlob is false or a "name GLOB pattern" match
 +** when pAr->bGlob is true.
  **
 -**   +  Input comes from p->in.
 -**   +  Store results in p->z of length p->n.  Space to hold p->z comes
 -**      from sqlite3_malloc64().
 -**   +  Use p->cSep as the column separator.  The default is "\x1F".
 -**   +  Use p->rSep as the row separator.  The default is "\x1E".
 -**   +  Keep track of the row number in p->nLine.
 -**   +  Store the character that terminates the field in p->cTerm.  Store
 -**      EOF on end-of-file.
 -**   +  Report syntax errors on stderr
 +** This function strips any trailing '/' characters from each argument.
 +** This is consistent with the way the [tar] command seems to work on
 +** Linux.
  */
 -static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){
 -  int c;
 -  int cSep = (u8)p->cColSep;
 -  int rSep = (u8)p->cRowSep;
 -  p->n = 0;
 -  c = fgetc(p->in);
 -  if( c==EOF || seenInterrupt ){
 -    p->cTerm = EOF;
 -    return 0;
 -  }
 -  while( c!=EOF && c!=cSep && c!=rSep ){
 -    import_append_char(p, c);
 -    c = fgetc(p->in);
 -  }
 -  if( c==rSep ){
 -    p->nLine++;
 +static int arCheckEntries(ArCommand *pAr){
 +  int rc = SQLITE_OK;
 +  if( pAr->nArg ){
 +    int i, j;
 +    sqlite3_stmt *pTest = 0;
 +    const char *zSel = (pAr->bGlob)
 +      ? "SELECT name FROM %s WHERE glob($name,name)"
 +      : "SELECT name FROM %s WHERE name=$name";
 +
 +    shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable);
 +    j = sqlite3_bind_parameter_index(pTest, "$name");
 +    for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
 +      char *z = pAr->azArg[i];
 +      int n = strlen30(z);
 +      int bOk = 0;
 +      while( n>0 && z[n-1]=='/' ) n--;
 +      z[n] = '\0';
 +      sqlite3_bind_text(pTest, j, z, -1, SQLITE_STATIC);
 +      if( SQLITE_ROW==sqlite3_step(pTest) ){
 +        bOk = 1;
 +      }
 +      shellReset(&rc, pTest);
 +      if( rc==SQLITE_OK && bOk==0 ){
 +        utf8_printf(STD_ERR, "not found in archive: %s\n", z);
 +        rc = SQLITE_ERROR;
 +      }
 +    }
 +    shellFinalize(&rc, pTest);
    }
 -  p->cTerm = c;
 -  if( p->z ) p->z[p->n] = 0;
 -  return p->z;
 +  return rc;
  }
  
  /*
@@@ -7710,950 -6256,654 +7833,950 @@@ static int recoverSqlCb(void *pCtx, con
  }
  
  /*
 -** Implementation of the ".dbinfo" command.
 -**
 -** Return 1 on error, 2 to exit, and 0 otherwise.
 +** This function is called to recover data from the database. A script
 +** to construct a new database containing all recovered data is output
 +** on stream pState->out.
  */
 -static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
 -  static const struct { const char *zName; int ofst; } aField[] = {
 -     { "file change counter:",  24  },
 -     { "database page count:",  28  },
 -     { "freelist page count:",  36  },
 -     { "schema cookie:",        40  },
 -     { "schema format:",        44  },
 -     { "default cache size:",   48  },
 -     { "autovacuum top root:",  52  },
 -     { "incremental vacuum:",   64  },
 -     { "text encoding:",        56  },
 -     { "user version:",         60  },
 -     { "application id:",       68  },
 -     { "software version:",     96  },
 -  };
 -  static const struct { const char *zName; const char *zSql; } aQuery[] = {
 -     { "number of tables:",
 -       "SELECT count(*) FROM %s WHERE type='table'" },
 -     { "number of indexes:",
 -       "SELECT count(*) FROM %s WHERE type='index'" },
 -     { "number of triggers:",
 -       "SELECT count(*) FROM %s WHERE type='trigger'" },
 -     { "number of views:",
 -       "SELECT count(*) FROM %s WHERE type='view'" },
 -     { "schema size:",
 -       "SELECT total(length(sql)) FROM %s" },
 -  };
 -  int i, rc;
 -  unsigned iDataVersion;
 -  char *zSchemaTab;
 -  char *zDb = nArg>=2 ? azArg[1] : "main";
 -  sqlite3_stmt *pStmt = 0;
 -  unsigned char aHdr[100];
 -  open_db(p, 0);
 -  if( p->db==0 ) return 1;
 -  rc = sqlite3_prepare_v2(p->db,
 -             "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1",
 -             -1, &pStmt, 0);
 -  if( rc ){
 -    utf8_printf(stderr, "error: %s\n", sqlite3_errmsg(p->db));
 -    sqlite3_finalize(pStmt);
 -    return 1;
 +static int recoverDatabaseCmd(ShellInState *pState, int nArg, char **azArg){
 +  int rc = SQLITE_OK;
 +  const char *zRecoveryDb = "";   /* Name of "recovery" database.  Debug only */
 +  const char *zLAF = "lost_and_found";
 +  int bFreelist = 1;              /* 0 if --ignore-freelist is specified */
 +  int bRowids = 1;                /* 0 if --no-rowids */
 +  sqlite3_recover *p = 0;
 +  int i = 0;
 +
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    int n;
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    n = strlen30(z);
 +    if( n<=17 && memcmp("-ignore-freelist", z, n)==0 ){
 +      bFreelist = 0;
 +    }else
 +    if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
 +      /* This option determines the name of the ATTACH-ed database used
 +      ** internally by the recovery extension.  The default is "" which
 +      ** means to use a temporary database that is automatically deleted
 +      ** when closed.  This option is undocumented and might disappear at
 +      ** any moment. */
 +      i++;
 +      zRecoveryDb = azArg[i];
 +    }else
 +    if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
 +      i++;
 +      zLAF = azArg[i];
 +    }else
 +    if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
 +      bRowids = 0;
 +    }
 +    else{
 +      utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
 +      showHelp(pState->out, azArg[0]);
 +      return 1;
 +    }
    }
 -  sqlite3_bind_text(pStmt, 1, zDb, -1, SQLITE_STATIC);
 -  if( sqlite3_step(pStmt)==SQLITE_ROW
 -   && sqlite3_column_bytes(pStmt,0)>100
 -  ){
 -    const u8 *pb = sqlite3_column_blob(pStmt,0);
 -    shell_check_oom(pb);
 -    memcpy(aHdr, pb, 100);
 -    sqlite3_finalize(pStmt);
 -  }else{
 -    raw_printf(stderr, "unable to read database header\n");
 -    sqlite3_finalize(pStmt);
 -    return 1;
 +
 +  p = sqlite3_recover_init_sql(
 +      pState->db, "main", recoverSqlCb, (void*)pState
 +  );
 +
 +  sqlite3_recover_config(p, 789, (void*)zRecoveryDb);  /* Debug use only */
 +  sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF);
 +  sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
 +  sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
 +
 +  sqlite3_recover_run(p);
 +  if( sqlite3_recover_errcode(p)!=SQLITE_OK ){
 +    const char *zErr = sqlite3_recover_errmsg(p);
 +    int errCode = sqlite3_recover_errcode(p);
 +    raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode);
    }
 -  i = get2byteInt(aHdr+16);
 -  if( i==1 ) i = 65536;
 -  utf8_printf(p->out, "%-20s %d\n", "database page size:", i);
 -  utf8_printf(p->out, "%-20s %d\n", "write format:", aHdr[18]);
 -  utf8_printf(p->out, "%-20s %d\n", "read format:", aHdr[19]);
 -  utf8_printf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
 -  for(i=0; i<ArraySize(aField); i++){
 -    int ofst = aField[i].ofst;
 -    unsigned int val = get4byteInt(aHdr + ofst);
 -    utf8_printf(p->out, "%-20s %u", aField[i].zName, val);
 -    switch( ofst ){
 -      case 56: {
 -        if( val==1 ) raw_printf(p->out, " (utf8)");
 -        if( val==2 ) raw_printf(p->out, " (utf16le)");
 -        if( val==3 ) raw_printf(p->out, " (utf16be)");
 +  rc = sqlite3_recover_finish(p);
 +  return rc;
 +}
 +#endif /* SQLITE_SHELL_HAVE_RECOVER */
 +
 +#ifndef SQLITE_SHELL_FIDDLE
 +static DotCmdRC
 +writeDb( char *azArg[], int nArg, ShellExState *psx, char **pzErr ){
 +  int rc = 0;
 +  const char *zDestFile = 0;
 +  const char *zDb = 0;
 +  sqlite3 *pDest;
 +  sqlite3_backup *pBackup;
 +  int j;
 +  int bAsync = 0;
 +  const char *zVfs = 0;
 +  if( ISS(psx)->bSafeMode ) return DCR_AbortError;
 +  for(j=1; j<nArg; j++){
 +    const char *z = azArg[j];
 +    if( z[0]=='-' ){
 +      if( z[1]=='-' ) z++;
 +      if( cli_strcmp(z, "-append")==0 ){
 +        zVfs = "apndvfs";
 +      }else if( cli_strcmp(z, "-async")==0 ){
 +        bAsync = 1;
 +      }else{
 +        return DCR_Unknown|j;
        }
 +    }else if( zDestFile==0 ){
 +      zDestFile = azArg[j];
 +    }else if( zDb==0 ){
 +      zDb = zDestFile;
 +      zDestFile = azArg[j];
 +    }else{
 +      return DCR_TooMany|j;
      }
 -    raw_printf(p->out, "\n");
    }
 -  if( zDb==0 ){
 -    zSchemaTab = sqlite3_mprintf("main.sqlite_schema");
 -  }else if( cli_strcmp(zDb,"temp")==0 ){
 -    zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema");
 -  }else{
 -    zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb);
 +  if( zDestFile==0 ){
 +    return DCR_Missing;
    }
 -  for(i=0; i<ArraySize(aQuery); i++){
 -    char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab);
 -    int val = db_int(p->db, zSql);
 -    sqlite3_free(zSql);
 -    utf8_printf(p->out, "%-20s %d\n", aQuery[i].zName, val);
 +  if( zDb==0 ) zDb = "main";
 +  rc = sqlite3_open_v2(zDestFile, &pDest,
 +                       SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
 +  if( rc!=SQLITE_OK ){
 +    *pzErr = smprintf("cannot open \"%s\"\n", zDestFile);
 +    close_db(pDest);
 +    return DCR_Error;
    }
 -  sqlite3_free(zSchemaTab);
 -  sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
 -  utf8_printf(p->out, "%-20s %u\n", "data version", iDataVersion);
 -  return 0;
 +  if( bAsync ){
 +    sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 +                 0, 0, 0);
 +  }
 +  open_db(psx, 0);
 +  pBackup = sqlite3_backup_init(pDest, "main", DBX(psx), zDb);
 +  if( pBackup==0 ){
 +    *pzErr = smprintf("%s failed, %s\n", azArg[0], sqlite3_errmsg(pDest));
 +    close_db(pDest);
 +    return DCR_Error;
 +  }
 +  while(  (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
 +  sqlite3_backup_finish(pBackup);
 +  if( rc==SQLITE_DONE ){
 +    rc = 0;
 +  }else{
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(pDest));
 +    rc = 1;
 +  }
 +  close_db(pDest);
 +  return DCR_Ok|rc;
  }
 -#endif /* SQLITE_SHELL_HAVE_RECOVER */
 +#endif /* !defined(SQLITE_SHELL_FIDDLE) */
  
  /*
 -** Print the current sqlite3_errmsg() value to stderr and return 1.
 -*/
 -static int shellDatabaseError(sqlite3 *db){
 -  const char *zErr = sqlite3_errmsg(db);
 -  utf8_printf(stderr, "Error: %s\n", zErr);
 -  return 1;
 + * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
 + * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
 + *   close db and set it to 0, and return the columns spec, to later
 + *   be sqlite3_free()'ed by the caller.
 + * The return is 0 when either:
 + *   (a) The db was not initialized and zCol==0 (There are no columns.)
 + *   (b) zCol!=0  (Column was added, db initialized as needed.)
 + * The 3rd argument, pRenamed, references an out parameter. If the
 + * pointer is non-zero, its referent will be set to a summary of renames
 + * done if renaming was necessary, or set to 0 if none was done. The out
 + * string (if any) must be sqlite3_free()'ed by the caller.
 + */
 +#ifdef SHELL_DEBUG
 +#define rc_err_oom_die(rc) \
 +  if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
 +  else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
 +    fprintf(STD_ERR,"E:%d\n",rc), assert(0)
 +#else
 +static void rc_err_oom_die(int rc){
 +  if( rc==SQLITE_NOMEM ) shell_check_oom(0);
 +  assert(rc==SQLITE_OK||rc==SQLITE_DONE);
  }
 +#endif
  
 -/*
 -** Compare the pattern in zGlob[] against the text in z[].  Return TRUE
 -** if they match and FALSE (0) if they do not match.
 -**
 -** Globbing rules:
 -**
 -**      '*'       Matches any sequence of zero or more characters.
 -**
 -**      '?'       Matches exactly one character.
 -**
 -**     [...]      Matches one character from the enclosed list of
 -**                characters.
 -**
 -**     [^...]     Matches one character not in the enclosed list.
 -**
 -**      '#'       Matches any sequence of one or more digits with an
 -**                optional + or - sign in front
 -**
 -**      ' '       Any span of whitespace matches any other span of
 -**                whitespace.
 -**
 -** Extra whitespace at the end of z[] is ignored.
 -*/
 -static int testcase_glob(const char *zGlob, const char *z){
 -  int c, c2;
 -  int invert;
 -  int seen;
 +#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
 +static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
 +#else  /* Otherwise, memory is faster/better for the transient DB. */
 +static const char *zCOL_DB = ":memory:";
 +#endif
  
 -  while( (c = (*(zGlob++)))!=0 ){
 -    if( IsSpace(c) ){
 -      if( !IsSpace(*z) ) return 0;
 -      while( IsSpace(*zGlob) ) zGlob++;
 -      while( IsSpace(*z) ) z++;
 -    }else if( c=='*' ){
 -      while( (c=(*(zGlob++))) == '*' || c=='?' ){
 -        if( c=='?' && (*(z++))==0 ) return 0;
 -      }
 -      if( c==0 ){
 -        return 1;
 -      }else if( c=='[' ){
 -        while( *z && testcase_glob(zGlob-1,z)==0 ){
 -          z++;
 -        }
 -        return (*z)!=0;
 -      }
 -      while( (c2 = (*(z++)))!=0 ){
 -        while( c2!=c ){
 -          c2 = *(z++);
 -          if( c2==0 ) return 0;
 -        }
 -        if( testcase_glob(zGlob,z) ) return 1;
 -      }
 -      return 0;
 -    }else if( c=='?' ){
 -      if( (*(z++))==0 ) return 0;
 -    }else if( c=='[' ){
 -      int prior_c = 0;
 -      seen = 0;
 -      invert = 0;
 -      c = *(z++);
 -      if( c==0 ) return 0;
 -      c2 = *(zGlob++);
 -      if( c2=='^' ){
 -        invert = 1;
 -        c2 = *(zGlob++);
 -      }
 -      if( c2==']' ){
 -        if( c==']' ) seen = 1;
 -        c2 = *(zGlob++);
 -      }
 -      while( c2 && c2!=']' ){
 -        if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
 -          c2 = *(zGlob++);
 -          if( c>=prior_c && c<=c2 ) seen = 1;
 -          prior_c = 0;
 -        }else{
 -          if( c==c2 ){
 -            seen = 1;
 -          }
 -          prior_c = c2;
 -        }
 -        c2 = *(zGlob++);
 -      }
 -      if( c2==0 || (seen ^ invert)==0 ) return 0;
 -    }else if( c=='#' ){
 -      if( (z[0]=='-' || z[0]=='+') && IsDigit(z[1]) ) z++;
 -      if( !IsDigit(z[0]) ) return 0;
 -      z++;
 -      while( IsDigit(z[0]) ){ z++; }
 +/* Define character (as C string) to separate generated column ordinal
 + * from protected part of incoming column names. This defaults to "_"
 + * so that incoming column identifiers that did not need not be quoted
 + * remain usable without being quoted. It must be one character.
 + */
 +#ifndef SHELL_AUTOCOLUMN_SEP
 +# define AUTOCOLUMN_SEP "_"
 +#else
 +# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
 +#endif
 +
 +static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
 +  /* Queries and D{D,M}L used here */
 +  static const char * const zTabMake = "\
 +CREATE TABLE ColNames(\
 + cpos INTEGER PRIMARY KEY,\
 + name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
 +CREATE VIEW RepeatedNames AS \
 +SELECT DISTINCT t.name FROM ColNames t \
 +WHERE t.name COLLATE NOCASE IN (\
 + SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
 +);\
 +";
 +  static const char * const zTabFill = "\
 +INSERT INTO ColNames(name,nlen,chop,reps,suff)\
 + VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
 +";
 +  static const char * const zHasDupes = "\
 +SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
 + <count(name) FROM ColNames\
 +";
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +  static const char * const zDedoctor = "\
 +UPDATE ColNames SET chop=iif(\
 +  (substring(name,nlen,1) BETWEEN '0' AND '9')\
 +  AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
 + nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
 + 0\
 +)\
 +";
 +#endif
 +  static const char * const zSetReps = "\
 +UPDATE ColNames AS t SET reps=\
 +(SELECT count(*) FROM ColNames d \
 + WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
 + COLLATE NOCASE\
 +)\
 +";
 +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
 +  static const char * const zColDigits = "\
 +SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
 +";
 +#else
 +  /* Counting on SQLITE_MAX_COLUMN < 100,000 here. (32767 is the hard limit.) */
 +  static const char * const zColDigits = "\
 +SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \
 + WHEN (nc < 1000) THEN 3 WHEN (nc < 10000) THEN 4 \
 + ELSE 5 FROM (SELECT count(*) AS nc FROM ColNames) \
 +";
 +#endif
 +  static const char * const zRenameRank =
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +    "UPDATE ColNames AS t SET suff="
 +    "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
 +#else /* ...RENAME_MINIMAL_ONE_PASS */
 +"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
 +"  SELECT 0 AS nlz"
 +"  UNION"
 +"  SELECT nlz+1 AS nlz FROM Lzn"
 +"  WHERE EXISTS("
 +"   SELECT 1"
 +"   FROM ColNames t, ColNames o"
 +"   WHERE"
 +"    iif(t.name IN (SELECT * FROM RepeatedNames),"
 +"     printf('%s"AUTOCOLUMN_SEP"%s',"
 +"      t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
 +"     t.name"
 +"    )"
 +"    ="
 +"    iif(o.name IN (SELECT * FROM RepeatedNames),"
 +"     printf('%s"AUTOCOLUMN_SEP"%s',"
 +"      o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
 +"     o.name"
 +"    )"
 +"    COLLATE NOCASE"
 +"    AND o.cpos<>t.cpos"
 +"   GROUP BY t.cpos"
 +"  )"
 +") UPDATE Colnames AS t SET"
 +" chop = 0," /* No chopping, never touch incoming names. */
 +" suff = iif(name IN (SELECT * FROM RepeatedNames),"
 +"  printf('"AUTOCOLUMN_SEP"%s', substring("
 +"   printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
 +"  ''"
 +" )"
 +#endif
 +    ;
 +  static const char * const zCollectVar = "\
 +SELECT\
 + '('||x'0a'\
 + || group_concat(\
 +  cname||' TEXT',\
 +  ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
 + ||')' AS ColsSpec \
 +FROM (\
 + SELECT cpos, printf('\"%w\"',printf('%!.*s%s',nlen-chop,name,suff)) AS cname \
 + FROM ColNames ORDER BY cpos\
 +)";
 +  static const char * const zRenamesDone =
 +    "SELECT group_concat("
 +    " printf('\"%w\" to \"%w\"',name,printf('%!.*s%s', nlen-chop, name, suff)),"
 +    " ','||x'0a')"
 +    "FROM ColNames WHERE suff<>'' OR chop!=0"
 +    ;
 +  int rc;
 +  sqlite3_stmt *pStmt = 0;
 +  assert(pDb!=0);
 +  if( zColNew ){
 +    /* Add initial or additional column. Init db if necessary. */
 +    if( *pDb==0 ){
 +      if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
 +#ifdef SHELL_COLFIX_DB
 +      if(*zCOL_DB!=':')
 +        sqlite3_exec(*pDb,"drop table if exists ColNames;"
 +                     "drop view if exists RepeatedNames;",0,0,0);
 +#endif
 +      rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +    }
 +    assert(*pDb!=0);
 +    rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_step(pStmt);
 +    rc_err_oom_die(rc);
 +    sqlite3_finalize(pStmt);
 +    return 0;
 +  }else if( *pDb==0 ){
 +    return 0;
 +  }else{
 +    /* Formulate the columns spec, close the DB, zero *pDb. */
 +    char *zColsSpec = 0;
 +    int hasDupes = db_int(*pDb, zHasDupes);
 +    int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
 +    if( hasDupes ){
 +#ifdef SHELL_COLUMN_RENAME_CLEAN
 +      rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +#endif
 +      rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +      rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
 +      rc_err_oom_die(rc);
 +      sqlite3_bind_int(pStmt, 1, nDigits);
 +      rc = sqlite3_step(pStmt);
 +      sqlite3_finalize(pStmt);
-       assert(rc==SQLITE_DONE);
++      if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM);
 +    }
 +    /* This assert is maybe overly cautious for above de-dup DML, but that can
 +     * be replaced via #define's. So this check is made for debug builds. */
 +    assert(db_int(*pDb, zHasDupes)==0);
 +    rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_step(pStmt);
 +    if( rc==SQLITE_ROW ){
 +      zColsSpec = smprintf("%s", sqlite3_column_text(pStmt, 0));
      }else{
 -      if( c!=(*(z++)) ) return 0;
 +      zColsSpec = 0;
 +    }
 +    if( pzRenamed!=0 ){
 +      if( !hasDupes ) *pzRenamed = 0;
 +      else{
 +        sqlite3_finalize(pStmt);
 +        if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
 +            && SQLITE_ROW==sqlite3_step(pStmt) ){
 +          *pzRenamed = smprintf("%s", sqlite3_column_text(pStmt, 0));
 +        }else
 +          *pzRenamed = 0;
 +      }
      }
 +    sqlite3_finalize(pStmt);
 +    sqlite3_close(*pDb);
 +    *pDb = 0;
 +    return zColsSpec;
    }
 -  while( IsSpace(*z) ){ z++; }
 -  return *z==0;
  }
  
 +#if SHELL_DATAIO_EXT
  
  /*
 -** Compare the string as a command-line option with either one or two
 -** initial "-" characters.
 +** Standard ExportHandlers
 +** These implement the built-in renderers of query results.
 +** Two are provided, one for freeform results, the other for columnar results.
  */
 -static int optionMatch(const char *zStr, const char *zOpt){
 -  if( zStr[0]!='-' ) return 0;
 -  zStr++;
 -  if( zStr[0]=='-' ) zStr++;
 -  return cli_strcmp(zStr, zOpt)==0;
 -}
  
 -/*
 -** Delete a file.
 -*/
 -int shellDeleteFile(const char *zFilename){
 -  int rc;
 -#ifdef _WIN32
 -  wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename);
 -  rc = _wunlink(z);
 -  sqlite3_free(z);
 -#else
 -  rc = unlink(zFilename);
 -#endif
 -  return rc;
 -}
 +static void EH_FF_destruct(ExportHandler *pMe);
 +static void EH_CM_destruct(ExportHandler *pMe);
 +static const char *EH_FF_name(ExportHandler *pMe);
 +static const char *EH_CM_name(ExportHandler *pMe);
 +static const char *EH_help(ExportHandler *pMe, const char *zWhat);
 +static int EH_openResultsOutStream(ExportHandler *pMe,
 +                                   ShellExState *pSES, char **pzErr,
 +                                   int numArgs, char *azArgs[],
 +                                   const char * zName);
 +static int EH_FF_prependResultsOut(ExportHandler *pMe,
 +                                   ShellExState *pSES, char **pzErr,
 +                                   sqlite3_stmt *pStmt);
 +static int EH_CM_prependResultsOut(ExportHandler *pMe,
 +                                   ShellExState *pSES, char **pzErr,
 +                                   sqlite3_stmt *pStmt);
 +static int EH_FF_rowResultsOut(ExportHandler *pMe,
 +                               ShellExState *pSES, char **pzErr,
 +                               sqlite3_stmt *pStmt);
 +static int EH_CM_rowResultsOut(ExportHandler *pMe,
 +                               ShellExState *pSES, char **pzErr,
 +                               sqlite3_stmt *pStmt);
 +static int EH_FF_appendResultsOut(ExportHandler *pMe,
 +                                  ShellExState *pSES, char **pzErr,
 +                                  sqlite3_stmt *pStmt);
 +static int EH_CM_appendResultsOut(ExportHandler *pMe,
 +                                  ShellExState *pSES, char **pzErr,
 +                                  sqlite3_stmt *pStmt);
 +static void EH_closeResultsOutStream(ExportHandler *pMe,
 +                                     ShellExState *pSES,
 +                                     char **pzErr);
 +
 +static VTABLE_NAME(ExportHandler) exporter_Vtab_FF = {
 +  EH_FF_destruct,
 +  EH_FF_name,
 +  EH_help,
 +  EH_openResultsOutStream,
 +  EH_FF_prependResultsOut,
 +  EH_FF_rowResultsOut,
 +  EH_FF_appendResultsOut,
 +  EH_closeResultsOutStream
 +};
  
 -/*
 -** Try to delete the temporary file (if there is one) and free the
 -** memory used to hold the name of the temp file.
 -*/
 -static void clearTempFile(ShellState *p){
 -  if( p->zTempFile==0 ) return;
 -  if( p->doXdgOpen ) return;
 -  if( shellDeleteFile(p->zTempFile) ) return;
 -  sqlite3_free(p->zTempFile);
 -  p->zTempFile = 0;
 -}
 +static VTABLE_NAME(ExportHandler) exporter_Vtab_CM = {
 +  EH_CM_destruct,
 +  EH_CM_name,
 +  EH_help,
 +  EH_openResultsOutStream,
 +  EH_CM_prependResultsOut,
 +  EH_CM_rowResultsOut,
 +  EH_CM_appendResultsOut,
 +  EH_closeResultsOutStream
 +};
  
 -/*
 -** Create a new temp file name with the given suffix.
 -*/
 -static void newTempFile(ShellState *p, const char *zSuffix){
 -  clearTempFile(p);
 -  sqlite3_free(p->zTempFile);
 -  p->zTempFile = 0;
 -  if( p->db ){
 -    sqlite3_file_control(p->db, 0, SQLITE_FCNTL_TEMPFILENAME, &p->zTempFile);
 -  }
 -  if( p->zTempFile==0 ){
 -    /* If p->db is an in-memory database then the TEMPFILENAME file-control
 -    ** will not work and we will need to fallback to guessing */
 -    char *zTemp;
 -    sqlite3_uint64 r;
 -    sqlite3_randomness(sizeof(r), &r);
 -    zTemp = getenv("TEMP");
 -    if( zTemp==0 ) zTemp = getenv("TMP");
 -    if( zTemp==0 ){
 -#ifdef _WIN32
 -      zTemp = "\\tmp";
 -#else
 -      zTemp = "/tmp";
 -#endif
 -    }
 -    p->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
 -  }else{
 -    p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix);
 -  }
 -  shell_check_oom(p->zTempFile);
 +typedef struct {
 +  char **azCols; /* Names of result columns */
 +  char **azVals; /* Results */
 +  int *aiTypes;  /* Result types */
 +} ColumnsInfo;
 +
 +typedef struct {
 +  VTABLE_NAME(ExportHandler) *pMethods;
 +  ShellInState *psi;
 +  int nCol;
 +  sqlite3_uint64 nRow;
 +  void *pData;
 +  void *pRowInfo;
 +  ColumnsInfo colInfo;
 +} BuiltInFFExporter;
 +#define BI_FF_EXPORTER_INIT(psi) { & exporter_Vtab_FF, psi, 0, 0, 0, 0 }
 +
 +typedef struct {
 +  VTABLE_NAME(ExportHandler) *pMethods;
 +  ShellInState *psi;
 +  int nCol;
 +  sqlite3_uint64 nRow;
 +  void *pData;
 +  void *pRowInfo;
 +  const char *colSep;
 +  const char *rowSep;
 +} BuiltInCMExporter;
 +#define BI_CM_EXPORTER_INIT(psi) { & exporter_Vtab_CM, psi, 0, 0, 0, 0 }
 +
 +static void EH_FF_destruct(ExportHandler *pMe){
 +  /* This serves two purposes: idempotent reinitialize, and final takedown */
 +  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 +  if( pbie->nCol!=0 ){
 +    sqlite3_free(pbie->pData);
 +    pbie->pData = 0;
 +  }
 +  pbie->nRow = 0;
 +  pbie->nCol = 0;
  }
  
 +static const char *zEmpty = "";
 +
 +static void EH_CM_destruct(ExportHandler *pMe){
 +  /* This serves two purposes: idempotent reinitialize, and final takedown */
 +  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 +  if( pbie->nCol!=0 ){
 +    sqlite3_uint64 nData = pbie->nCol * (pbie->nRow + 1), i;
 +    const char *zNull = pbie->psi->nullValue;
 +    char **azData = (char**)pbie->pData;
 +    for(i=0; i<nData; i++){
 +      char *z = azData[i];
 +      if( z!=zEmpty && z!=zNull ) sqlite3_free(z);
 +    }
 +    sqlite3_free(pbie->pData);
 +    sqlite3_free(pbie->pRowInfo);
 +    pbie->pData = 0;
 +    pbie->pRowInfo = 0;
 +  }
 +  pbie->nCol = 0;
 +  pbie->nRow = 0;
 +  pbie->colSep = 0;
 +  pbie->rowSep = 0;
 +}
  
 -/*
 -** The implementation of SQL scalar function fkey_collate_clause(), used
 -** by the ".lint fkey-indexes" command. This scalar function is always
 -** called with four arguments - the parent table name, the parent column name,
 -** the child table name and the child column name.
 -**
 -**   fkey_collate_clause('parent-tab', 'parent-col', 'child-tab', 'child-col')
 -**
 -** If either of the named tables or columns do not exist, this function
 -** returns an empty string. An empty string is also returned if both tables
 -** and columns exist but have the same default collation sequence. Or,
 -** if both exist but the default collation sequences are different, this
 -** function returns the string " COLLATE <parent-collation>", where
 -** <parent-collation> is the default collation sequence of the parent column.
 -*/
 -static void shellFkeyCollateClause(
 -  sqlite3_context *pCtx,
 -  int nVal,
 -  sqlite3_value **apVal
 -){
 -  sqlite3 *db = sqlite3_context_db_handle(pCtx);
 -  const char *zParent;
 -  const char *zParentCol;
 -  const char *zParentSeq;
 -  const char *zChild;
 -  const char *zChildCol;
 -  const char *zChildSeq = 0;  /* Initialize to avoid false-positive warning */
 -  int rc;
 -
 -  assert( nVal==4 );
 -  zParent = (const char*)sqlite3_value_text(apVal[0]);
 -  zParentCol = (const char*)sqlite3_value_text(apVal[1]);
 -  zChild = (const char*)sqlite3_value_text(apVal[2]);
 -  zChildCol = (const char*)sqlite3_value_text(apVal[3]);
 -
 -  sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC);
 -  rc = sqlite3_table_column_metadata(
 -      db, "main", zParent, zParentCol, 0, &zParentSeq, 0, 0, 0
 -  );
 -  if( rc==SQLITE_OK ){
 -    rc = sqlite3_table_column_metadata(
 -        db, "main", zChild, zChildCol, 0, &zChildSeq, 0, 0, 0
 -    );
 -  }
 -
 -  if( rc==SQLITE_OK && sqlite3_stricmp(zParentSeq, zChildSeq) ){
 -    char *z = sqlite3_mprintf(" COLLATE %s", zParentSeq);
 -    sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
 -    sqlite3_free(z);
 -  }
 +static const char *zModeName(ShellInState *psi){
 +  int mi = psi->mode;
 +  return (mi>=0 && mi<MODE_COUNT_OF)? modeDescr[mi].zModeName : 0;
 +}
 +static const char *EH_FF_name(ExportHandler *pMe){
 +  return zModeName(((BuiltInFFExporter*)pMe)->psi);
 +}
 +static const char *EH_CM_name(ExportHandler *pMe){
 +  return zModeName(((BuiltInCMExporter*)pMe)->psi);
  }
  
 +static const char *EH_help(ExportHandler *pMe, const char *zWhat){
 +  (void)(pMe);
 +  (void)(zWhat);
 +  return 0;
 +}
  
 -/*
 -** The implementation of dot-command ".lint fkey-indexes".
 -*/
 -static int lintFkeyIndexes(
 -  ShellState *pState,             /* Current shell tool state */
 -  char **azArg,                   /* Array of arguments passed to dot command */
 -  int nArg                        /* Number of entries in azArg[] */
 -){
 -  sqlite3 *db = pState->db;       /* Database handle to query "main" db of */
 -  FILE *out = pState->out;        /* Stream to write non-error output to */
 -  int bVerbose = 0;               /* If -verbose is present */
 -  int bGroupByParent = 0;         /* If -groupbyparent is present */
 -  int i;                          /* To iterate through azArg[] */
 -  const char *zIndent = "";       /* How much to indent CREATE INDEX by */
 -  int rc;                         /* Return code */
 -  sqlite3_stmt *pSql = 0;         /* Compiled version of SQL statement below */
 +static int EH_openResultsOutStream(ExportHandler *pMe,
 +                                   ShellExState *pSES, char **pzErr,
 +                                   int numArgs, char *azArgs[],
 +                                   const char * zName){
 +  /* The built-in exporters have a predetermined destination, and their
 +   * action is set by the shell state .mode member, so this method has
 +   * nothing to do. For similar reasons, the shell never calls it. That
 +   * could change if .mode command functionality is moved to here.
 +   */
 +  (void)(pMe);
 +  (void)(pSES);
 +  (void)(pzErr);
 +  (void)(numArgs);
 +  (void)(azArgs);
 +  (void)(zName);
 +  return 0;
 +}
  
 -  /*
 -  ** This SELECT statement returns one row for each foreign key constraint
 -  ** in the schema of the main database. The column values are:
 -  **
 -  ** 0. The text of an SQL statement similar to:
 -  **
 -  **      "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
 -  **
 -  **    This SELECT is similar to the one that the foreign keys implementation
 -  **    needs to run internally on child tables. If there is an index that can
 -  **    be used to optimize this query, then it can also be used by the FK
 -  **    implementation to optimize DELETE or UPDATE statements on the parent
 -  **    table.
 -  **
 -  ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
 -  **    the EXPLAIN QUERY PLAN command matches this pattern, then the schema
 -  **    contains an index that can be used to optimize the query.
 -  **
 -  ** 2. Human readable text that describes the child table and columns. e.g.
 -  **
 -  **       "child_table(child_key1, child_key2)"
 -  **
 -  ** 3. Human readable text that describes the parent table and columns. e.g.
 -  **
 -  **       "parent_table(parent_key1, parent_key2)"
 -  **
 -  ** 4. A full CREATE INDEX statement for an index that could be used to
 -  **    optimize DELETE or UPDATE statements on the parent table. e.g.
 -  **
 -  **       "CREATE INDEX child_table_child_key ON child_table(child_key)"
 -  **
 -  ** 5. The name of the parent table.
 -  **
 -  ** These six values are used by the C logic below to generate the report.
 -  */
 -  const char *zSql =
 -  "SELECT "
 -    "     'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
 -    "  || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
 -    "  || fkey_collate_clause("
 -    "       f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
 -    ", "
 -    "     'SEARCH ' || s.name || ' USING COVERING INDEX*('"
 -    "  || group_concat('*=?', ' AND ') || ')'"
 -    ", "
 -    "     s.name  || '(' || group_concat(f.[from],  ', ') || ')'"
 -    ", "
 -    "     f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
 -    ", "
 -    "     'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
 -    "  || ' ON ' || quote(s.name) || '('"
 -    "  || group_concat(quote(f.[from]) ||"
 -    "        fkey_collate_clause("
 -    "          f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
 -    "  || ');'"
 -    ", "
 -    "     f.[table] "
 -    "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
 -    "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
 -    "GROUP BY s.name, f.id "
 -    "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
 -  ;
 -  const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
 +static int EH_CM_prependResultsOut(ExportHandler *pMe,
 +                                   ShellExState *psx, char **pzErr,
 +                                   sqlite3_stmt *pStmt){
 +  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 +  ShellInState *psi = ISS(psx);
 +  sqlite3_int64 nRow = 0;
 +  char **azData = 0;
 +  sqlite3_int64 nAlloc = 0;
 +  char *abRowDiv = 0;
 +  const unsigned char *uz;
 +  const char *z;
 +  char **azQuoted = 0;
 +  int rc;
 +  sqlite3_int64 i, nData;
 +  int j, w, n;
 +  const unsigned char **azNextLine = 0;
 +  int bNextLine = 0;
 +  int bMultiLineRowExists = 0;
 +  int bw = psi->cmOpts.bWordWrap;
 +  int nColumn = sqlite3_column_count(pStmt);
  
 -  for(i=2; i<nArg; i++){
 -    int n = strlen30(azArg[i]);
 -    if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
 -      bVerbose = 1;
 +  if( nColumn==0 ){
 +    rc = sqlite3_step(pStmt);
 +    assert(rc!=SQLITE_ROW);
 +    return rc;
 +  }
 +  EH_CM_destruct(pMe);
 +
 +  nAlloc = nColumn*4;
 +  if( nAlloc<=0 ) nAlloc = 1;
 +  azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
 +  shell_check_oom(azData);
 +  azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
 +  shell_check_oom((void*)azNextLine);
 +  memset((void*)azNextLine, 0, nColumn*sizeof(char*) );
 +  if( psi->cmOpts.bQuote ){
 +    azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) );
 +    shell_check_oom(azQuoted);
 +    memset(azQuoted, 0, nColumn*sizeof(char*) );
 +  }
 +  abRowDiv = sqlite3_malloc64( nAlloc/nColumn );
 +  shell_check_oom(abRowDiv);
 +  if( nColumn>psx->numWidths ){
 +    psx->pSpecWidths = realloc(psx->pSpecWidths, (nColumn+1)*2*sizeof(int));
 +    shell_check_oom(psx->pSpecWidths);
 +    for(i=psx->numWidths; i<nColumn; i++) psx->pSpecWidths[i] = 0;
 +    psx->numWidths = nColumn;
 +    psx->pHaveWidths = &psx->pSpecWidths[nColumn];
 +  }
 +  memset(psx->pHaveWidths, 0, nColumn*sizeof(int));
 +  for(i=0; i<nColumn; i++){
 +    w = psx->pSpecWidths[i];
 +    if( w<0 ) w = -w;
 +    psx->pHaveWidths[i] = w;
 +  }
 +  for(i=0; i<nColumn; i++){
 +    const unsigned char *zNotUsed;
 +    int wx = psx->pSpecWidths[i];
 +    if( wx==0 ){
 +      wx = psi->cmOpts.iWrap;
      }
 -    else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
 -      bGroupByParent = 1;
 -      zIndent = "    ";
 +    if( wx<0 ) wx = -wx;
 +    uz = (const unsigned char*)sqlite3_column_name(pStmt,i);
 +    azData[i] = translateForDisplayAndDup(uz, &zNotUsed, wx, bw);
 +  }
 +  while( bNextLine || SQLITE_ROW==sqlite3_step(pStmt) ){
 +    int useNextLine = bNextLine;
 +    bNextLine = 0;
 +    if( (nRow+2)*nColumn >= nAlloc ){
 +      nAlloc *= 2;
 +      azData = sqlite3_realloc64(azData, nAlloc*sizeof(char*));
 +      shell_check_oom(azData);
 +      abRowDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn);
 +      shell_check_oom(abRowDiv);
      }
 -    else{
 -      raw_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
 -          azArg[0], azArg[1]
 -      );
 -      return SQLITE_ERROR;
 +    abRowDiv[nRow] = 1;
 +    nRow++;
 +    for(i=0; i<nColumn; i++){
 +      int wx = psx->pSpecWidths[i];
 +      if( wx==0 ){
 +        wx = psi->cmOpts.iWrap;
 +      }
 +      if( wx<0 ) wx = -wx;
 +      if( useNextLine ){
 +        uz = azNextLine[i];
 +        if( uz==0 ) uz = (u8*)zEmpty;
 +      }else if( psi->cmOpts.bQuote ){
 +        sqlite3_free(azQuoted[i]);
 +        azQuoted[i] = quoted_column(pStmt,i);
 +        uz = (const unsigned char*)azQuoted[i];
 +      }else{
 +        uz = (const unsigned char*)sqlite3_column_text(pStmt,i);
 +        if( uz==0 ) uz = (u8*)psi->nullValue;
 +      }
 +      azData[nRow*nColumn + i]
 +        = translateForDisplayAndDup(uz, &azNextLine[i], wx, bw);
 +      if( azNextLine[i] ){
 +        bNextLine = 1;
 +        abRowDiv[nRow-1] = 0;
 +        bMultiLineRowExists = 1;
 +      }
      }
    }
 -
 -  /* Register the fkey_collate_clause() SQL function */
 -  rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
 -      0, shellFkeyCollateClause, 0, 0
 -  );
 -
 -
 -  if( rc==SQLITE_OK ){
 -    rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0);
 +  sqlite3_free((void*)azNextLine);
 +  if( azQuoted ){
 +    for(i=0; i<nColumn; i++) sqlite3_free(azQuoted[i]);
 +    sqlite3_free(azQuoted);
    }
 -  if( rc==SQLITE_OK ){
 -    sqlite3_bind_int(pSql, 1, bGroupByParent);
 +  if( nRow==0 ){
 +    EH_CM_destruct(pMe);
 +    return SQLITE_DONE;
    }
  
 -  if( rc==SQLITE_OK ){
 -    int rc2;
 -    char *zPrev = 0;
 -    while( SQLITE_ROW==sqlite3_step(pSql) ){
 -      int res = -1;
 -      sqlite3_stmt *pExplain = 0;
 -      const char *zEQP = (const char*)sqlite3_column_text(pSql, 0);
 -      const char *zGlob = (const char*)sqlite3_column_text(pSql, 1);
 -      const char *zFrom = (const char*)sqlite3_column_text(pSql, 2);
 -      const char *zTarget = (const char*)sqlite3_column_text(pSql, 3);
 -      const char *zCI = (const char*)sqlite3_column_text(pSql, 4);
 -      const char *zParent = (const char*)sqlite3_column_text(pSql, 5);
 -
 -      if( zEQP==0 ) continue;
 -      if( zGlob==0 ) continue;
 -      rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
 -      if( rc!=SQLITE_OK ) break;
 -      if( SQLITE_ROW==sqlite3_step(pExplain) ){
 -        const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3);
 -        res = zPlan!=0 && (  0==sqlite3_strglob(zGlob, zPlan)
 -                          || 0==sqlite3_strglob(zGlobIPK, zPlan));
 -      }
 -      rc = sqlite3_finalize(pExplain);
 -      if( rc!=SQLITE_OK ) break;
 +  nData = nColumn*(nRow+1);
  
 -      if( res<0 ){
 -        raw_printf(stderr, "Error: internal error");
 -        break;
 -      }else{
 -        if( bGroupByParent
 -        && (bVerbose || res==0)
 -        && (zPrev==0 || sqlite3_stricmp(zParent, zPrev))
 -        ){
 -          raw_printf(out, "-- Parent table %s\n", zParent);
 -          sqlite3_free(zPrev);
 -          zPrev = sqlite3_mprintf("%s", zParent);
 +  for(i=0; i<nData; i++){
 +    z = azData[i];
 +    if( z==0 ) z = (char*)zEmpty;
 +    n = strlenChar(z);
 +    j = i%nColumn;
 +    if( n>psx->pHaveWidths[j] ) psx->pHaveWidths[j] = n;
 +  }
 +  if( seenInterrupt ) goto done;
 +  switch( psi->cMode ){
 +    case MODE_Column: {
 +      pbie->colSep = "  ";
 +      pbie->rowSep = "\n";
 +      if( psi->showHeader ){
 +        for(i=0; i<nColumn; i++){
 +          w = psx->pHaveWidths[i];
 +          if( psx->pSpecWidths[i]<0 ) w = -w;
 +          utf8_width_print(psi->out, w, azData[i]);
 +          fputs(i==nColumn-1?"\n":"  ", psi->out);
          }
 -
 -        if( res==0 ){
 -          raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget);
 -        }else if( bVerbose ){
 -          raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n",
 -              zIndent, zFrom, zTarget
 -          );
 +        for(i=0; i<nColumn; i++){
 +          print_dashes(psi->out, psx->pHaveWidths[i]);
 +          fputs(i==nColumn-1?"\n":"  ", psi->out);
          }
        }
 +      break;
      }
 -    sqlite3_free(zPrev);
 -
 -    if( rc!=SQLITE_OK ){
 -      raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
 +    case MODE_Table: {
 +      pbie->colSep = " | ";
 +      pbie->rowSep = " |\n";
 +      print_row_separator(psx, nColumn, "+");
 +      fputs("| ", psi->out);
 +      for(i=0; i<nColumn; i++){
 +        w = psx->pHaveWidths[i];
 +        n = strlenChar(azData[i]);
 +        utf8_printf(psi->out, "%*s%s%*s",
 +                    (w-n)/2, "", azData[i], (w-n+1)/2, "");
 +        fputs(i==nColumn-1?" |\n":" | ", psi->out);
 +      }
 +      print_row_separator(psx, nColumn, "+");
 +      break;
      }
 -
 -    rc2 = sqlite3_finalize(pSql);
 -    if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
 -      rc = rc2;
 -      raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
 +    case MODE_Markdown: {
 +      pbie->colSep = " | ";
 +      pbie->rowSep = " |\n";
 +      fputs("| ", psi->out);
 +      for(i=0; i<nColumn; i++){
 +        w = psx->pHaveWidths[i];
 +        n = strlenChar(azData[i]);
 +        utf8_printf(psi->out, "%*s%s%*s",
 +                    (w-n)/2, "", azData[i], (w-n+1)/2, "");
 +        fputs(i==nColumn-1?" |\n":" | ", psi->out);
 +      }
 +      print_row_separator(psx, nColumn, "|");
 +      break;
 +    }
 +    case MODE_Box: {
 +      pbie->colSep = " " BOX_13 " ";
 +      pbie->rowSep = " " BOX_13 "\n";
 +      print_box_row_separator(psx, nColumn, BOX_23, BOX_234, BOX_34);
 +      utf8_printf(psi->out, BOX_13 " ");
 +      for(i=0; i<nColumn; i++){
 +        w = psx->pHaveWidths[i];
 +        n = strlenChar(azData[i]);
 +        utf8_printf(psi->out, "%*s%s%*s%s",
 +            (w-n)/2, "", azData[i], (w-n+1)/2, "",
 +            i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
 +      }
 +      print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
 +      break;
      }
 +  }
 + done:
 +  pbie->nCol = nColumn;
 +  pbie->pData = azData;
 +  pbie->nRow = nRow;
 +  if( bMultiLineRowExists ){
 +    pbie->pRowInfo = abRowDiv;
    }else{
 -    raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
 +    pbie->pRowInfo = 0;
 +    sqlite3_free(abRowDiv);
    }
 -
 -  return rc;
 +  if( seenInterrupt ){
 +    EH_CM_destruct(pMe);
 +    return SQLITE_INTERRUPT;
 +  }
 +  return SQLITE_OK;
  }
  
 -/*
 -** Implementation of ".lint" dot command.
 -*/
 -static int lintDotCommand(
 -  ShellState *pState,             /* Current shell tool state */
 -  char **azArg,                   /* Array of arguments passed to dot command */
 -  int nArg                        /* Number of entries in azArg[] */
 -){
 -  int n;
 -  n = (nArg>=2 ? strlen30(azArg[1]) : 0);
 -  if( n<1 || sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ) goto usage;
 -  return lintFkeyIndexes(pState, azArg, nArg);
 -
 - usage:
 -  raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]);
 -  raw_printf(stderr, "Where sub-commands are:\n");
 -  raw_printf(stderr, "    fkey-indexes\n");
 -  return SQLITE_ERROR;
 -}
 +static int EH_CM_rowResultsOut(ExportHandler *pMe,
 +                               ShellExState *psx, char **pzErr,
 +                               sqlite3_stmt *pStmt){
 +  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 +  ShellInState *psi = pbie->psi;
 +  sqlite3_int64 nRow = pbie->nRow;
 +  int nColumn = pbie->nCol, j, w;
 +  char **azData = (char**)(pbie->pData);
 +  sqlite3_int64 nData = (nRow+1)*nColumn, i;
 +  char *abRowDiv = pbie->pRowInfo;
 +  const char *z;
  
 -#if !defined SQLITE_OMIT_VIRTUALTABLE
 -static void shellPrepare(
 -  sqlite3 *db,
 -  int *pRc,
 -  const char *zSql,
 -  sqlite3_stmt **ppStmt
 -){
 -  *ppStmt = 0;
 -  if( *pRc==SQLITE_OK ){
 -    int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
 -    if( rc!=SQLITE_OK ){
 -      raw_printf(stderr, "sql error: %s (%d)\n",
 -          sqlite3_errmsg(db), sqlite3_errcode(db)
 -      );
 -      *pRc = rc;
 +  (void)(pzErr);
 +  (void)(pStmt);
 +  if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
 +  for(i=nColumn, j=0; i<nData; i++, j++){
 +    if( j==0 && psi->cMode!=MODE_Column ){
 +      utf8_printf(psi->out, "%s", psi->cMode==MODE_Box?BOX_13" ":"| ");
 +    }
 +    z = azData[i];
 +    if( z==0 ) z = zEmpty;
 +    w = psx->pHaveWidths[j];
 +    if( psx->pSpecWidths[j]<0 ) w = -w;
 +    utf8_width_print(psi->out, w, z);
 +    if( j==nColumn-1 ){
 +      utf8_printf(psi->out, "%s", pbie->rowSep);
 +      if( abRowDiv!=0 && abRowDiv[i/nColumn-1] && i+1<nData ){
 +        if( psi->cMode==MODE_Table ){
 +          print_row_separator(psx, nColumn, "+");
 +        }else if( psi->cMode==MODE_Box ){
 +          print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
 +        }else if( psi->cMode==MODE_Column ){
 +          raw_printf(psi->out, "\n");
 +        }
 +      }
 +      j = -1;
 +      if( seenInterrupt ){
 +        EH_CM_destruct(pMe);
 +        return SQLITE_INTERRUPT;
 +      }
 +    }else{
 +      utf8_printf(psi->out, "%s", pbie->colSep);
      }
    }
 +  return SQLITE_DONE;
  }
  
 -/*
 -** Create a prepared statement using printf-style arguments for the SQL.
 -**
 -** This routine is could be marked "static".  But it is not always used,
 -** depending on compile-time options.  By omitting the "static", we avoid
 -** nuisance compiler warnings about "defined but not used".
 -*/
 -void shellPreparePrintf(
 -  sqlite3 *db,
 -  int *pRc,
 -  sqlite3_stmt **ppStmt,
 -  const char *zFmt,
 -  ...
 -){
 -  *ppStmt = 0;
 -  if( *pRc==SQLITE_OK ){
 -    va_list ap;
 -    char *z;
 -    va_start(ap, zFmt);
 -    z = sqlite3_vmprintf(zFmt, ap);
 -    va_end(ap);
 -    if( z==0 ){
 -      *pRc = SQLITE_NOMEM;
 +static int EH_CM_appendResultsOut(ExportHandler *pMe,
 +                                  ShellExState *psx, char **pzErr,
 +                                  sqlite3_stmt *pStmt){
 +  BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
 +  ShellInState *psi = ISS(psx);
 +  sqlite3_int64 nRow = pbie->nRow;
 +  int nColumn = pbie->nCol;
 +  char **azData = (char**)(pbie->pData);
 +  sqlite3_int64 nData = (nRow+1)*nColumn;
 +
 +  if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
 +
 +  if( psi->cMode==MODE_Table ){
 +    print_row_separator(psx, nColumn, "+");
 +  }else if( psi->cMode==MODE_Box ){
 +    print_box_row_separator(psx, nColumn, BOX_12, BOX_124, BOX_14);
 +  }
 +  EH_CM_destruct(pMe);
 +  return SQLITE_OK;
 +}
 +
 +static int EH_FF_prependResultsOut(ExportHandler *pMe,
 +                                   ShellExState *pSES, char **pzErr,
 +                                   sqlite3_stmt *pStmt){
 +  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 +  int nc = sqlite3_column_count(pStmt);
 +  int rc;
 +
 +  pbie->pMethods->destruct(pMe);
 +  if( nc>0 ){
 +    /* allocate space for col name ptr, value ptr, and type */
 +    pbie->pData = sqlite3_malloc64(3*nc*sizeof(const char*) + 1);
 +    if( !pbie->pData ){
 +      shell_out_of_memory();
      }else{
 -      shellPrepare(db, pRc, z, ppStmt);
 -      sqlite3_free(z);
 +      ColumnsInfo ci
 +        = { (char **)pbie->pData, &ci.azCols[nc], (int *)&ci.azVals[nc] };
 +      int i;
 +      assert(sizeof(int) <= sizeof(char *));
 +      pbie->nCol = nc;
 +      pbie->colInfo = ci;
 +      /* save off ptrs to column names */
 +      for(i=0; i<nc; i++){
 +        pbie->colInfo.azCols[i] = (char *)sqlite3_column_name(pStmt, i);
 +      }
      }
 +    return SQLITE_OK;
    }
 +  rc = sqlite3_step(pStmt);
 +  assert(rc!=SQLITE_ROW);
 +  return rc;
  }
  
 -/* Finalize the prepared statement created using shellPreparePrintf().
 -**
 -** This routine is could be marked "static".  But it is not always used,
 -** depending on compile-time options.  By omitting the "static", we avoid
 -** nuisance compiler warnings about "defined but not used".
 -*/
 -void shellFinalize(
 -  int *pRc,
 -  sqlite3_stmt *pStmt
 -){
 -  if( pStmt ){
 -    sqlite3 *db = sqlite3_db_handle(pStmt);
 -    int rc = sqlite3_finalize(pStmt);
 -    if( *pRc==SQLITE_OK ){
 -      if( rc!=SQLITE_OK ){
 -        raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
 +static int EH_FF_rowResultsOut(ExportHandler *pMe,
 +                               ShellExState *pSES, char **pzErr,
 +                               sqlite3_stmt *pStmt){
 +  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 +  ShellInState *psi = ISS(pSES);
 +  int rc = sqlite3_step(pStmt);
 +  int i, x, nc = pbie->nCol;
 +  if( rc==SQLITE_ROW ){
 +    ColumnsInfo *pc = &pbie->colInfo;
 +    sqlite3_uint64 nr = ++(pbie->nRow);
 +    for( i=0; i<nc; ++i ){
 +      pc->aiTypes[i] = x = sqlite3_column_type(pStmt, i);
 +      if( x==SQLITE_BLOB
 +          && (psi->cMode==MODE_Insert || psi->cMode==MODE_Quote) ){
 +        pc->azVals[i] = "";
 +      }else{
 +        pc->azVals[i] = (char*)sqlite3_column_text(pStmt, i);
 +      }
 +      if( !pc->azVals[i] && (x!=SQLITE_NULL) ){
 +        rc = SQLITE_NOMEM;
 +        break; /* from for */
 +      }
 +    }
 +    /* if data and types extracted successfully... */
 +    if( SQLITE_ROW==rc ){
 +      /* call the supplied callback with the result row data */
 +      if( shell_callback(pSES, nc, pc->azVals, pc->azCols, pc->aiTypes) ){
 +        rc = SQLITE_ABORT;
        }
 -      *pRc = rc;
      }
    }
 +  return rc;
 +}
 +
 +static int EH_FF_appendResultsOut(ExportHandler *pMe,
 +                                  ShellExState *pSES, char **pzErr,
 +                                  sqlite3_stmt *pStmt){
 +  BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
 +  ShellInState *psi = ISS(pSES);
 +  if( psi->cMode==MODE_Json ){
 +    fputs("]\n", psi->out);
 +  }else if( psi->cMode==MODE_Count ){
 +    utf8_printf(psi->out, "%llu row%s\n", pbie->nRow, pbie->nRow!=1 ? "s" : "");
 +  }
 +  EH_FF_destruct(pMe);
 +  return SQLITE_OK;
 +}
 +
 +static void EH_closeResultsOutStream(ExportHandler *pMe,
 +                                     ShellExState *pSES,
 +                                     char **pzErr){
 +  /* The built-in exporters have a predetermined destination which is
 +   * never "closed", so this method has nothing to do. For similar
 +   * reasons, it is not called by the shell.
 +   */
 +  (void)(pMe);
 +  (void)(pSES);
 +  (void)(pzErr);
  }
 +#endif /* SHELL_DATAIO_EXT */
  
 -/* Reset the prepared statement created using shellPreparePrintf().
 -**
 -** This routine is could be marked "static".  But it is not always used,
 -** depending on compile-time options.  By omitting the "static", we avoid
 -** nuisance compiler warnings about "defined but not used".
 -*/
 -void shellReset(
 -  int *pRc,
 -  sqlite3_stmt *pStmt
 -){
 -  int rc = sqlite3_reset(pStmt);
 -  if( *pRc==SQLITE_OK ){
 -    if( rc!=SQLITE_OK ){
 -      sqlite3 *db = sqlite3_db_handle(pStmt);
 -      raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
 +#if SHELL_DYNAMIC_EXTENSION
 +
 +/* Ensure there is room in loaded extension info list for one being loaded.
 + * On exit, psi->ixExtPending can be used to index into psi->pShxLoaded.
 + */
 +static ShExtInfo *pending_ext_info(ShellInState *psi){
 +  int ixpe = psi->ixExtPending;
 +  assert(ixpe!=0);
 +  if( ixpe >= psi->numExtLoaded ){
 +    psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded,
 +                                      (ixpe+1)*sizeof(ShExtInfo));
 +    shell_check_oom(psi->pShxLoaded);
 +    ++psi->numExtLoaded;
 +    memset(psi->pShxLoaded+ixpe, 0, sizeof(ShExtInfo));
 +  }
 +  return &psi->pShxLoaded[ixpe];
 +}
 +
 +/* Register a dot-command, to be called during extension load/init. */
 +static int register_dot_command(ShellExState *p,
 +                                ExtensionId eid, DotCommand *pMC){
 +  ShellInState *psi = ISS(p);
 +  ShExtInfo *psei = pending_ext_info(psi);
 +  const char *zSql
 +    = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, ?, ?)";
 +  int ie = psi->ixExtPending;
 +  assert(psi->pShxLoaded!=0 && p->dbShell!=0);
 +  if( pMC==0 ) return SQLITE_ERROR;
 +  else{
 +    const char *zName = pMC->pMethods->name(pMC);
 +    sqlite3_stmt *pStmt;
 +    int nc = psei->numDotCommands;
 +    int rc;
 +    if( psei->extId!=0 && psei->extId!=eid ) return SQLITE_MISUSE;
 +    psei->extId = eid;
 +    rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0);
 +    if( rc!=SQLITE_OK ) return rc;
 +    psei->ppDotCommands
 +      = sqlite3_realloc(psei->ppDotCommands, (nc+1)*sizeof(DotCommand *));
 +    shell_check_oom(psei->ppDotCommands);
 +    sqlite3_bind_text(pStmt, 1, zName, -1, 0);
 +    sqlite3_bind_int(pStmt, 2, ie);
 +    sqlite3_bind_int(pStmt, 3, nc);
 +    rc = sqlite3_step(pStmt);
 +    sqlite3_finalize(pStmt);
 +    if( rc==SQLITE_DONE ){
 +      psei->ppDotCommands[nc++] = pMC;
 +      psei->numDotCommands = nc;
 +      notify_subscribers(psi, NK_NewDotCommand, pMC);
 +      if( cli_strcmp("unknown", zName)==0 ){
 +        psi->pUnknown = pMC;
 +        psei->pUnknown = pMC;
 +      }
 +      return SQLITE_OK;
 +    }else{
 +      psei->ppDotCommands[nc] = 0;
      }
 -    *pRc = rc;
    }
 +  return SQLITE_ERROR;
  }
 -#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */
  
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 -/******************************************************************************
 -** The ".archive" or ".ar" command.
 -*/
 -/*
 -** Structure representing a single ".ar" command.
 -*/
 -typedef struct ArCommand ArCommand;
 -struct ArCommand {
 -  u8 eCmd;                        /* An AR_CMD_* value */
 -  u8 bVerbose;                    /* True if --verbose */
 -  u8 bZip;                        /* True if the archive is a ZIP */
 -  u8 bDryRun;                     /* True if --dry-run */
 -  u8 bAppend;                     /* True if --append */
 -  u8 bGlob;                       /* True if --glob */
 -  u8 fromCmdLine;                 /* Run from -A instead of .archive */
 -  int nArg;                       /* Number of command arguments */
 -  char *zSrcTable;                /* "sqlar", "zipfile($file)" or "zip" */
 -  const char *zFile;              /* --file argument, or NULL */
 -  const char *zDir;               /* --directory argument, or NULL */
 -  char **azArg;                   /* Array of command arguments */
 -  ShellState *p;                  /* Shell state */
 -  sqlite3 *db;                    /* Database containing the archive */
 -};
 +/* Register an output data display (or other disposition) mode */
 +static int register_exporter(ShellExState *p,
 +                             ExtensionId eid, ExportHandler *pEH){
 +  return SQLITE_ERROR;
 +}
  
 -/*
 -** Print a usage message for the .ar command to stderr and return SQLITE_ERROR.
 -*/
 -static int arUsage(FILE *f){
 -  showHelp(f,"archive");
 +/* Register an import variation from (various sources) for .import */
 +static int register_importer(ShellExState *p,
 +                             ExtensionId eid, ImportHandler *pIH){
    return SQLITE_ERROR;
  }
  
@@@ -9072,3541 -7178,2077 +9195,3571 @@@ static int load_shell_extension(ShellEx
    }
    return rc;
  }
 +#endif
  
 -/*
 -** Format a WHERE clause that can be used against the "sqlar" table to
 -** identify all archive members that match the command arguments held
 -** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning.
 -** The caller is responsible for eventually calling sqlite3_free() on
 -** any non-NULL (*pzWhere) value. Here, "match" means strict equality
 -** when pAr->bGlob is false and GLOB match when pAr->bGlob is true.
 +/* Dot-command implementation functions are defined in this section.
 +COMMENT  Define dot-commands and provide for their dispatch and .help text.
 +COMMENT  These should be kept in command name order for coding convenience
 +COMMENT  except where dot-commands share implementation. (The ordering
 +COMMENT  required for dispatch and help text is effected regardless.) The
 +COMMENT  effect of this configuration can be seen in generated output or by
 +COMMENT  executing tool/mkshellc.tcl --parameters (or --details or --help).
 +COMMENT  Generally, this section defines dispatchable functions inline and
 +COMMENT  causes collection of command_table entry initializers, to be later
 +COMMENT  emitted by a mkshellc macro. (See EMIT_DOTCMD_INIT further on.)
 +** All dispatchable dot-command execute functions have this signature:
 +static int someCommand(char *azArg[], int nArg, ShellExState *p, char **pzErr);
  */
 -static void arWhereClause(
 -  int *pRc,
 -  ArCommand *pAr,
 -  char **pzWhere                  /* OUT: New WHERE clause */
 -){
 -  char *zWhere = 0;
 -  const char *zSameOp = (pAr->bGlob)? "GLOB" : "=";
 -  if( *pRc==SQLITE_OK ){
 -    if( pAr->nArg==0 ){
 -      zWhere = sqlite3_mprintf("1");
 -    }else{
 -      int i;
 -      const char *zSep = "";
 -      for(i=0; i<pAr->nArg; i++){
 -        const char *z = pAr->azArg[i];
 -        zWhere = sqlite3_mprintf(
 -          "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'",
 -          zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z
 -        );
 -        if( zWhere==0 ){
 -          *pRc = SQLITE_NOMEM;
 -          break;
 -        }
 -        zSep = " OR ";
 -      }
 -    }
 +DISPATCH_CONFIG[
 +  RETURN_TYPE=DotCmdRC
 +  STORAGE_CLASS=static
 +  ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellExState *$arg6, char **$arg7
 +  DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 },
 +  DOTCMD_INIT={ DOT_CMD_INFO(${cmd}, $arg1,$arg2,$arg3), {<HT0>, <HT1>}, 0 },
 +  CMD_CAPTURE_RE=^\s*{\s*"(\w+)"
 +  DISPATCHEE_NAME=${cmd}Command
 +  DC_ARG1_DEFAULT=[string length $cmd]
 +  DC_ARG2_DEFAULT=0
 +  DC_ARG3_DEFAULT=0
 +  DC_ARG4_DEFAULT=azArg
 +  DC_ARG5_DEFAULT=nArg
 +  DC_ARG6_DEFAULT=p
 +  DC_ARG7_DEFAULT=pzErr
 +  DC_ARG_COUNT=8
 +];
 +
 +CONDITION_COMMAND(seeargs !defined(SHELL_OMIT_SEEARGS));
 +/*****************
 + * The .seeargs command
 + */
 +COLLECT_HELP_TEXT[
 +  ",seeargs                 Echo arguments suffixed with |",
 +];
 +DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){
 +  int ia = 0;
 +  for (ia=1; ia<nArg; ++ia)
 +    raw_printf(ISS(p)->out, "%s%s", azArg[ia], (ia==nArg-1)? "|\n" : "|");
 +  return DCR_Ok;
 +}
 +
 +/* Future: Make macro processor accept newline escapes to shorten this. */
 +CONDITION_COMMAND(archive !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_SHELL_FIDDLE));
 +/*****************
 + * The .archive command
 + */
 +COLLECT_HELP_TEXT[
 +  ".archive ...             Manage SQL archives",
 +  "   Each command must have exactly one of the following options:",
 +  "     -c, --create               Create a new archive",
 +  "     -u, --update               Add or update files with changed mtime",
 +  "     -i, --insert               Like -u but always add even if unchanged",
 +  "     -r, --remove               Remove files from archive",
 +  "     -t, --list                 List contents of archive",
 +  "     -x, --extract              Extract files from archive",
 +  "   Optional arguments:",
 +  "     -v, --verbose              Print each filename as it is processed",
 +  "     -f FILE, --file FILE       Use archive FILE (default is current db)",
 +  "     -a FILE, --append FILE     Open FILE using the apndvfs VFS",
 +  "     -C DIR, --directory DIR    Read/extract files from directory DIR",
 +  "     -g, --glob                 Use glob matching for names in archive",
 +  "     -n, --dryrun               Show the SQL that would have occurred",
 +  "   Examples:",
 +  "     .ar -cf ARCHIVE foo bar  # Create ARCHIVE from files foo and bar",
 +  "     .ar -tf ARCHIVE          # List members of ARCHIVE",
 +  "     .ar -xvf ARCHIVE         # Verbosely extract files from ARCHIVE",
 +  "   See also:",
 +  "      http://sqlite.org/cli.html#sqlite_archive_support",
 +];
 +DISPATCHABLE_COMMAND( archive ? 0 0 azArg nArg p ){
 +  open_db(p, 0);
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  return arDotCommand(p, 0, azArg, nArg);
 +}
 +
 +/*****************
 + * The .auth command
 + */
 +CONDITION_COMMAND(auth !defined(SQLITE_OMIT_AUTHORIZATION));
 +COLLECT_HELP_TEXT[
 +  ".auth ON|OFF             Show authorizer callbacks",
 +];
 +DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){
 +  open_db(p, 0);
 +  if( booleanValue(azArg[1]) ){
 +    sqlite3_set_authorizer(DBX(p), shellAuth, p);
 +  }else if( ISS(p)->bSafeModeFuture ){
 +    sqlite3_set_authorizer(DBX(p), safeModeAuth, p);
 +  }else{
 +    sqlite3_set_authorizer(DBX(p), 0, 0);
    }
 -  *pzWhere = zWhere;
 +  return DCR_Ok;
  }
  
 -/*
 -** Implementation of .ar "lisT" command.
 -*/
 -static int arListCommand(ArCommand *pAr){
 -  const char *zSql = "SELECT %s FROM %s WHERE %s";
 -  const char *azCols[] = {
 -    "name",
 -    "lsmode(mode), sz, datetime(mtime, 'unixepoch'), name"
 -  };
 +/*****************
 + * The .backup and .save commands (aliases for each other)
 + * These defer to writeDb in the dispatch table, so are not here.
 + */
 +CONDITION_COMMAND(backup !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(save !defined(SQLITE_SHELL_FIDDLE) );
 +COLLECT_HELP_TEXT[
 +  ".backup ?DB? FILE        Backup DB (default \"main\") to FILE",
 +  "   Options:",
 +  "     --append               Use the appendvfs",
 +  "     --async                Write the FILE without journal and fsync()",
 +  ".save ?DB? FILE          Write DB (default \"main\") to FILE",
 +  "   Options:",
 +  "     --append               Use the appendvfs",
 +  "     --async                Write the FILE without journal and fsync()",
 +];
 +DISPATCHABLE_COMMAND( backup 4 2 5 ){
 +  return writeDb( azArg, nArg, p, pzErr);
 +}
 +DISPATCHABLE_COMMAND( save 3 2 5 ){
 +  return writeDb( azArg, nArg, p, pzErr);
 +}
  
 -  char *zWhere = 0;
 -  sqlite3_stmt *pSql = 0;
 -  int rc;
 +/*****************
 + * The .bail command
 + */
 +COLLECT_HELP_TEXT[
 +  ".bail on|off             Stop after hitting an error.  Default OFF",
 +];
 +DISPATCHABLE_COMMAND( bail 3 2 2 ){
 +  bail_on_error = booleanValue(azArg[1]);
 +  return DCR_Ok;
 +}
  
 -  rc = arCheckEntries(pAr);
 -  arWhereClause(&rc, pAr, &zWhere);
 +CONDITION_COMMAND(cd !defined(SQLITE_SHELL_FIDDLE));
 +/*****************
 + * The .binary and .cd commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".binary on|off           Turn binary output on or off.  Default OFF",
 +  ".cd DIRECTORY            Change the working directory to DIRECTORY",
 +];
 +DISPATCHABLE_COMMAND( binary 3 2 2 ){
 +  if( booleanValue(azArg[1]) ){
 +    setBinaryMode(ISS(p)->out, 1);
 +  }else{
 +    setTextMode(ISS(p)->out, 1);
 +  }
 +  return DCR_Ok;
 +}
  
 -  shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose],
 -                     pAr->zSrcTable, zWhere);
 -  if( pAr->bDryRun ){
 -    utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql));
 +DISPATCHABLE_COMMAND( cd ? 2 2 ){
 +  int rc=0;
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +#if defined(_WIN32) || defined(WIN32)
 +  wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 +  rc = !SetCurrentDirectoryW(z);
 +  sqlite3_free(z);
 +#else
 +  rc = chdir(azArg[1]);
 +#endif
 +  if( rc ){
 +    utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]);
 +    rc = 1;
 +  }
 +  return DCR_Ok|rc;
 +}
 +
 +/* The ".breakpoint" command causes a call to the no-op routine named
 + * test_breakpoint(). It is undocumented.
 +*/
 +COLLECT_HELP_TEXT[
 +  ",breakpoint              calls test_breakpoint(). (a debugging aid)",
 +];
 +DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
 +  test_breakpoint();
 +  return DCR_Ok;
 +}
 +
 +CONDITION_COMMAND(check !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(clone !defined(SQLITE_SHELL_FIDDLE));
 +/*****************
 + * The .changes, .check, .clone and .connection commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".changes on|off          Show number of rows changed by SQL",
 +  ",check GLOB              Fail if output since .testcase does not match",
 +  ".clone NEWDB             Clone data into NEWDB from the existing database",
 +  ".connection [close] [#]  Open or close an auxiliary database connection",
 +];
 +DISPATCHABLE_COMMAND( changes 3 2 2 ){
 +  setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( check 3 0 0 ){
 +  /* Cancel output redirection, if it is currently set (by .testcase)
 +  ** Then read the content of the testcase-out.txt file and compare against
 +  ** azArg[1].  If there are differences, report an error and exit.
 +  */
 +  char *zRes = 0;
 +  int rc = 0;
 +  DotCmdRC rv = DCR_Ok;
 +  output_reset(ISS(p));
 +  if( nArg!=2 ){
 +    return DCR_ArgWrong;
 +  }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
-     *pzErr = shellMPrintf(&rc, "Error: cannot read 'testcase-out.txt'");
++    *pzErr = shellMPrintf(&rc, "Error: cannot read 'testcase-out.txt'\n");
 +    rv = DCR_Return;
 +  }else if( testcase_glob(azArg[1],zRes)==0 ){
 +    *pzErr =
 +      shellMPrintf(&rc,
 +                   "testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
 +                   ISS(p)->zTestcase, azArg[1], zRes);
 +    rv = DCR_Error;
    }else{
 -    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
 -      if( pAr->bVerbose ){
 -        utf8_printf(pAr->p->out, "%s % 10d  %s  %s\n",
 -            sqlite3_column_text(pSql, 0),
 -            sqlite3_column_int(pSql, 1),
 -            sqlite3_column_text(pSql, 2),
 -            sqlite3_column_text(pSql, 3)
 -        );
 -      }else{
 -        utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0));
 -      }
 +    utf8_printf(STD_OUT, "testcase-%s ok\n", ISS(p)->zTestcase);
 +    ISS(p)->nCheck++;
 +  }
 +  sqlite3_free(zRes);
 +  return (rc==SQLITE_NOMEM)? DCR_Abort : rv;
 +}
 +DISPATCHABLE_COMMAND( clone ? 2 2 ){
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  tryToClone(p, azArg[1]);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( connection ? 1 4 ){
 +  ShellInState *psi = ISS(p);
 +  if( nArg==1 ){
 +    /* List available connections */
 +    int i;
 +    for(i=0; i<ArraySize(psi->aAuxDb); i++){
 +      const char *zFile = psi->aAuxDb[i].zDbFilename;
 +      if( psi->aAuxDb[i].db==0 && psi->pAuxDb!=&psi->aAuxDb[i] ){
 +        zFile = "(not open)";
 +      }else if( zFile==0 ){
 +        zFile = "(memory)";
 +      }else if( zFile[0]==0 ){
 +        zFile = "(temporary-file)";
 +      }
 +      if( psi->pAuxDb == &psi->aAuxDb[i] ){
 +        utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile);
 +      }else if( psi->aAuxDb[i].db!=0 ){
 +        utf8_printf(STD_OUT, "       %d: %s\n", i, zFile);
 +      }
 +    }
 +  }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
 +    int i = azArg[1][0] - '0';
 +    if( psi->pAuxDb != &psi->aAuxDb[i] && i>=0 && i<ArraySize(psi->aAuxDb) ){
 +      psi->pAuxDb->db = DBI(psi);
 +      psi->pAuxDb = &psi->aAuxDb[i];
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserVanishing, DBI(psi));
 +#endif
 +      globalDb = DBI(psi) = psi->pAuxDb->db;
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserAppeared, DBI(psi));
 +#endif
 +      psi->pAuxDb->db = 0;
 +    }
 +  }else if( nArg==3 && cli_strcmp(azArg[1], "close")==0
 +            && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
 +    int i = azArg[2][0] - '0';
 +    if( i<0 || i>=ArraySize(psi->aAuxDb) ){
 +      /* No-op */
 +    }else if( psi->pAuxDb == &psi->aAuxDb[i] ){
 +      raw_printf(STD_ERR, "cannot close the active database connection\n");
 +      return DCR_Error;
 +    }else if( psi->aAuxDb[i].db ){
 +      session_close_all(psi, i);
 +      close_db(psi->aAuxDb[i].db);
 +      psi->aAuxDb[i].db = 0;
 +    }
 +  }else{
 +    return DCR_ArgWrong;
 +  }
 +  return DCR_Ok;
 +}
 +
 +CONDITION_COMMAND(dbinfo SQLITE_SHELL_HAVE_RECOVER);
 +/*****************
 + * The .databases, .dbconfig and .dbinfo commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".databases               List names and files of attached databases",
 +  ".dbconfig ?op? ?val?     List or change sqlite3_db_config() options",
 +  ".dbinfo ?DB?             Show status information about the database",
 +];
 +/* Allow garbage arguments on this, to be ignored. */
 +DISPATCHABLE_COMMAND( databases 2 1 0 ){
 +  int rc;
 +  char **azName = 0;
 +  int nName = 0;
 +  sqlite3_stmt *pStmt;
 +  sqlite3 *db;
 +  int i;
 +  open_db(p, 0);
 +  db = DBX(p);
 +  rc = sqlite3_prepare_v2(db, "PRAGMA database_list", -1, &pStmt, 0);
 +  if( rc ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 +    rc = 1;
 +  }else{
 +    while( sqlite3_step(pStmt)==SQLITE_ROW ){
 +      const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
 +      const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
 +      if( zSchema==0 || zFile==0 ) continue;
 +      azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
 +      shell_check_oom(azName);
 +      azName[nName*2] = strdup(zSchema);
 +      shell_check_oom(azName[nName*2]);
 +      azName[nName*2+1] = strdup(zFile);
 +      shell_check_oom(azName[nName*2+1]);
 +      nName++;
      }
    }
 -  shellFinalize(&rc, pSql);
 -  sqlite3_free(zWhere);
 -  return rc;
 +  sqlite3_finalize(pStmt);
 +  for(i=0; i<nName; i++){
 +    int eTxn = sqlite3_txn_state(db, azName[i*2]);
 +    int bRdonly = sqlite3_db_readonly(db, azName[i*2]);
 +    const char *z = azName[i*2+1];
 +    utf8_printf(ISS(p)->out, "%s: %s %s%s\n",
 +                azName[i*2],
 +                z && z[0] ? z : "\"\"",
 +                bRdonly ? "r/o" : "r/w",
 +                eTxn==SQLITE_TXN_NONE ? "" :
 +                eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
 +    free(azName[i*2]);
 +    free(azName[i*2+1]);
 +  }
 +  sqlite3_free(azName);
 +  return DCR_Ok|(rc!=0);
  }
 -
 -
 -/*
 -** Implementation of .ar "Remove" command.
 -*/
 -static int arRemoveCommand(ArCommand *pAr){
 -  int rc = 0;
 -  char *zSql = 0;
 -  char *zWhere = 0;
 -
 -  if( pAr->nArg ){
 -    /* Verify that args actually exist within the archive before proceeding.
 -    ** And formulate a WHERE clause to match them.  */
 -    rc = arCheckEntries(pAr);
 -    arWhereClause(&rc, pAr, &zWhere);
 +DISPATCHABLE_COMMAND( dbconfig 3 1 3 ){
 +  static const struct DbConfigChoices {
 +    const char *zName;
 +    int op;
 +  } aDbConfig[] = {
 +    { "defensive",          SQLITE_DBCONFIG_DEFENSIVE             },
 +    { "dqs_ddl",            SQLITE_DBCONFIG_DQS_DDL               },
 +    { "dqs_dml",            SQLITE_DBCONFIG_DQS_DML               },
 +    { "enable_fkey",        SQLITE_DBCONFIG_ENABLE_FKEY           },
 +    { "enable_qpsg",        SQLITE_DBCONFIG_ENABLE_QPSG           },
 +    { "enable_trigger",     SQLITE_DBCONFIG_ENABLE_TRIGGER        },
 +    { "enable_view",        SQLITE_DBCONFIG_ENABLE_VIEW           },
 +    { "fts3_tokenizer",     SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
 +    { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE    },
 +    { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT    },
 +    { "load_extension",     SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
 +    { "no_ckpt_on_close",   SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE      },
 +    { "reset_database",     SQLITE_DBCONFIG_RESET_DATABASE        },
++    { "reverse_scanorder",  SQLITE_DBCONFIG_REVERSE_SCANORDER     },
++    { "stmt_scanstatus",    SQLITE_DBCONFIG_STMT_SCANSTATUS       },
 +    { "trigger_eqp",        SQLITE_DBCONFIG_TRIGGER_EQP           },
 +    { "trusted_schema",     SQLITE_DBCONFIG_TRUSTED_SCHEMA        },
 +    { "writable_schema",    SQLITE_DBCONFIG_WRITABLE_SCHEMA       },
 +  };
 +  int ii, v;
 +  open_db(p, 0);
 +  for(ii=0; ii<ArraySize(aDbConfig); ii++){
 +    if( nArg>1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
 +    if( nArg>=3 ){
 +      sqlite3_db_config(DBX(p), aDbConfig[ii].op, booleanValue(azArg[2]), 0);
 +    }
 +    sqlite3_db_config(DBX(p), aDbConfig[ii].op, -1, &v);
 +    utf8_printf(ISS(p)->out, "%19s %s\n",
 +                aDbConfig[ii].zName, v ? "on" : "off");
 +    if( nArg>1 ) break;
    }
 -  if( rc==SQLITE_OK ){
 -    zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;",
 -                           pAr->zSrcTable, zWhere);
 -    if( pAr->bDryRun ){
 -      utf8_printf(pAr->p->out, "%s\n", zSql);
 -    }else{
 -      char *zErr = 0;
 -      rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0);
 -      if( rc==SQLITE_OK ){
 -        rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
 -        if( rc!=SQLITE_OK ){
 -          sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
 +  if( nArg>1 && ii==ArraySize(aDbConfig) ){
 +    *pzErr = smprintf("Error: unknown dbconfig \"%s\"\n"
 +                      "Enter \".dbconfig\" with no arguments for a list\n",
 +                      azArg[1]);
 +    return DCR_ArgWrong;
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){
 +  return shell_dbinfo_command(p, nArg, azArg);
 +}
 +
 +/*****************
 + * The .dump, .echo and .eqp commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".dump ?OBJECTS?          Render database content as SQL",
 +  "   Options:",
 +  "     --data-only            Output only INSERT statements",
 +  "     --newlines             Allow unescaped newline characters in output",
 +  "     --nosys                Omit system tables (ex: \"sqlite_stat1\")",
 +  "     --preserve-rowids      Include ROWID values in the output",
 +  "     --schema SCHEMA        Dump table(s) from given SCHEMA",
 +  "   OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
 +  "   Additional LIKE patterns can be given in subsequent arguments",
 +  ".echo on|off             Turn command echo on or off",
 +  ".eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN",
 +  "   Other Modes:",
 +#ifdef SQLITE_DEBUG
 +  "      test                  Show raw EXPLAIN QUERY PLAN output",
 +  "      trace                 Like \"full\" but enable \"PRAGMA vdbe_trace\"",
 +#endif
 +  "      trigger               Like \"full\" but also show trigger bytecode",
 +];
 +DISPATCHABLE_COMMAND( dump ? 1 2 ){
 +  ShellInState *psi = ISS(p);
 +  char *zLike = 0;
 +  char *zSchema = "main";
 +  char *zSql;
 +  int i;
 +  int savedShowHeader = psi->showHeader;
 +  int savedShellFlags = psi->shellFlgs;
 +  ShellClearFlag(p,
 +     SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
 +     |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
 +  for(i=1; i<nArg; i++){
 +    if( azArg[i][0]=='-' ){
 +      const char *z = azArg[i]+1;
 +      if( z[0]=='-' ) z++;
 +      if( cli_strcmp(z,"preserve-rowids")==0 ){
 +#ifdef SQLITE_OMIT_VIRTUALTABLE
 +        *pzErr = smprintf("The --preserve-rowids option is not compatible"
 +                          " with SQLITE_OMIT_VIRTUALTABLE\n");
 +        sqlite3_free(zLike);
 +        return DCR_ArgWrong;
 +#else
 +        ShellSetFlag(p, SHFLG_PreserveRowid);
 +#endif
 +      }else{
 +        if( cli_strcmp(z,"newlines")==0 ){
 +          ShellSetFlag(p, SHFLG_Newlines);
 +        }else if( cli_strcmp(z,"data-only")==0 ){
 +          ShellSetFlag(p, SHFLG_DumpDataOnly);
 +        }else if( cli_strcmp(z,"nosys")==0 ){
 +          ShellSetFlag(p, SHFLG_DumpNoSys);
 +        }else if( cli_strcmp(z,"schema")==0 && ++i<nArg ){
 +          zSchema = azArg[i];
          }else{
 -          rc = sqlite3_exec(pAr->db, "RELEASE ar;", 0, 0, 0);
 +          *pzErr = smprintf("Unknown option \"%s\" on \".dump\"\n", azArg[i]);
 +          sqlite3_free(zLike);
 +          return DCR_ArgWrong;
          }
        }
 -      if( zErr ){
 -        utf8_printf(stdout, "ERROR: %s\n", zErr);
 -        sqlite3_free(zErr);
 +    }else{
 +      /* azArg[i] contains a LIKE pattern. This ".dump" request should
 +      ** only dump data for tables for which either the table name matches
 +      ** the LIKE pattern, or the table appears to be a shadow table of
 +      ** a virtual table for which the name matches the LIKE pattern.
 +      */
 +      char *zExpr = smprintf(
 +                    "name LIKE %Q ESCAPE '\\' OR EXISTS ("
 +                    "  SELECT 1 FROM %w.sqlite_schema WHERE "
 +                    "    name LIKE %Q ESCAPE '\\' AND"
 +                    "    sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
 +                    "    substr(o.name, 1, length(name)+1) == (name||'_')"
 +                    ")", azArg[i], zSchema, azArg[i]
 +                    );
 +
 +      if( zLike ){
 +        zLike = smprintf("%z OR %z", zLike, zExpr);
 +      }else{
 +        zLike = zExpr;
        }
      }
    }
 -  sqlite3_free(zWhere);
 -  sqlite3_free(zSql);
 -  return rc;
 -}
 -
 -/*
 -** Implementation of .ar "eXtract" command.
 -*/
 -static int arExtractCommand(ArCommand *pAr){
 -  const char *zSql1 =
 -    "SELECT "
 -    " ($dir || name),"
 -    " writefile(($dir || name), %s, mode, mtime) "
 -    "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)"
 -    " AND name NOT GLOB '*..[/\\]*'";
 -
 -  const char *azExtraArg[] = {
 -    "sqlar_uncompress(data, sz)",
 -    "data"
 -  };
  
 -  sqlite3_stmt *pSql = 0;
 -  int rc = SQLITE_OK;
 -  char *zDir = 0;
 -  char *zWhere = 0;
 -  int i, j;
 +  open_db(p, 0);
  
 -  /* If arguments are specified, check that they actually exist within
 -  ** the archive before proceeding. And formulate a WHERE clause to
 -  ** match them.  */
 -  rc = arCheckEntries(pAr);
 -  arWhereClause(&rc, pAr, &zWhere);
 +  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    /* When playing back a "dump", the content might appear in an order
 +    ** which causes immediate foreign key constraints to be violated.
 +    ** So disable foreign-key constraint enforcement to prevent problems. */
 +    raw_printf(psi->out, "PRAGMA foreign_keys=OFF;\n");
 +    raw_printf(psi->out, "BEGIN TRANSACTION;\n");
 +  }
 +  psi->writableSchema = 0;
 +  psi->showHeader = 0;
 +  /* Set writable_schema=ON since doing so forces SQLite to initialize
 +  ** as much of the schema as it can even if the sqlite_schema table is
 +  ** corrupt. */
 +  sqlite3_exec(DBX(p), "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
 +  psi->nErr = 0;
 +  if( zLike==0 ) zLike = smprintf("true");
 +  zSql = smprintf("SELECT name, type, sql FROM %w.sqlite_schema AS o "
 +                  "WHERE (%s) AND type=='table' AND sql NOT NULL"
 +                  " ORDER BY tbl_name='sqlite_sequence', rowid",
 +                  zSchema, zLike);
 +  run_schema_dump_query(psi,zSql);
 +  sqlite3_free(zSql);
 +  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    zSql = smprintf(
 +             "SELECT sql FROM sqlite_schema AS o "
 +             "WHERE (%s) AND sql NOT NULL"
 +             "  AND type IN ('index','trigger','view')",
 +             zLike
 +           );
 +    run_table_dump_query(psi, zSql);
 +    sqlite3_free(zSql);
 +  }
 +  sqlite3_free(zLike);
 +  if( psi->writableSchema ){
 +    raw_printf(psi->out, "PRAGMA writable_schema=OFF;\n");
 +    psi->writableSchema = 0;
 +  }
 +  sqlite3_exec(DBX(p), "PRAGMA writable_schema=OFF;", 0, 0, 0);
 +  sqlite3_exec(DBX(p), "RELEASE dump;", 0, 0, 0);
 +  if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 +    raw_printf(psi->out, psi->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
 +  }
 +  psi->showHeader = savedShowHeader;
 +  psi->shellFlgs = savedShellFlags;
  
 -  if( rc==SQLITE_OK ){
 -    if( pAr->zDir ){
 -      zDir = sqlite3_mprintf("%s/", pAr->zDir);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( echo ? 2 2 ){
 +  setOrClearFlag(p, SHFLG_Echo, azArg[1]);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( eqp ? 0 0 ){
 +  ShellInState *psi = ISS(p);
 +  if( nArg==2 ){
 +    psi->autoEQPtest = 0;
 +    if( psi->autoEQPtrace ){
 +      if( DBX(p) ) sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
 +      psi->autoEQPtrace = 0;
 +    }
 +    if( cli_strcmp(azArg[1],"full")==0 ){
 +      psi->autoEQP = AUTOEQP_full;
 +    }else if( cli_strcmp(azArg[1],"trigger")==0 ){
 +      psi->autoEQP = AUTOEQP_trigger;
 +#ifdef SQLITE_DEBUG
 +    }else if( cli_strcmp(azArg[1],"test")==0 ){
 +      psi->autoEQP = AUTOEQP_on;
 +      psi->autoEQPtest = 1;
 +    }else if( cli_strcmp(azArg[1],"trace")==0 ){
 +      psi->autoEQP = AUTOEQP_full;
 +      psi->autoEQPtrace = 1;
 +      open_db(p, 0);
 +      sqlite3_exec(DBX(p), "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
 +      sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=ON;", 0, 0, 0);
 +#endif
      }else{
 -      zDir = sqlite3_mprintf("");
 +      psi->autoEQP = (u8)booleanValue(azArg[1]);
      }
 -    if( zDir==0 ) rc = SQLITE_NOMEM;
 +  }else{
 +    return DCR_ArgWrong;
    }
 +  return DCR_Ok;
 +}
  
 -  shellPreparePrintf(pAr->db, &rc, &pSql, zSql1,
 -      azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere
 -  );
 -
 -  if( rc==SQLITE_OK ){
 -    j = sqlite3_bind_parameter_index(pSql, "$dir");
 -    sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC);
 -
 -    /* Run the SELECT statement twice. The first time, writefile() is called
 -    ** for all archive members that should be extracted. The second time,
 -    ** only for the directories. This is because the timestamps for
 -    ** extracted directories must be reset after they are populated (as
 -    ** populating them changes the timestamp).  */
 -    for(i=0; i<2; i++){
 -      j = sqlite3_bind_parameter_index(pSql, "$dirOnly");
 -      sqlite3_bind_int(pSql, j, i);
 -      if( pAr->bDryRun ){
 -        utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql));
 -      }else{
 -        while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
 -          if( i==0 && pAr->bVerbose ){
 -            utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0));
 -          }
 -        }
 -      }
 -      shellReset(&rc, pSql);
 -    }
 -    shellFinalize(&rc, pSql);
 +CONDITION_COMMAND(cease !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(exit !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(quit !defined(SQLITE_SHELL_FIDDLE));
 +/*****************
 + * The .cease, .exit and .quit commands
 + * These are together so that their differing effects are apparent.
 + */
 +CONDITION_COMMAND(cease defined(SHELL_CEASE));
 +COLLECT_HELP_TEXT[
 +  ".cease ?CODE?            Cease shell operation, with optional return code",
 +  "   Return code defaults to 0, otherwise is limited to non-signal values",
 +  ".exit ?CODE?             Exit shell program, maybe with return-code CODE",
 +  "   Exit immediately if CODE != 0, else functions as \"quit this input\"",
-   ".quit                    Quit processing this input or script",
++  ".quit                    Stop interpreting input stream, exit if primary.",
 +];
 +DISPATCHABLE_COMMAND( cease 4 1 2 ){
 +  /* .cease effects an exit, always. Only the exit code is variable. */
 +  int rc = 0;
 +  if( nArg>1 ){
 +    rc = (int)integerValue(azArg[1]);
 +    if( rc>0x7f ) rc = 0x7f;
    }
 -
 -  sqlite3_free(zDir);
 -  sqlite3_free(zWhere);
 -  return rc;
 +  p->shellAbruptExit = 0x100|rc;
 +  return DCR_Exit;
  }
 -
 -/*
 -** Run the SQL statement in zSql.  Or if doing a --dryrun, merely print it out.
 -*/
 -static int arExecSql(ArCommand *pAr, const char *zSql){
 +DISPATCHABLE_COMMAND( exit 3 1 0 ){
 +  /* .exit acts like .quit with no argument or a zero argument,
 +   * only returning. With a non-zero argument, it effects an exit. */
    int rc;
 -  if( pAr->bDryRun ){
 -    utf8_printf(pAr->p->out, "%s\n", zSql);
 -    rc = SQLITE_OK;
 -  }else{
 -    char *zErr = 0;
 -    rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
 -    if( zErr ){
 -      utf8_printf(stdout, "ERROR: %s\n", zErr);
 -      sqlite3_free(zErr);
 -    }
 +  if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ){
 +    rc &= 0xff;    /* Mimic effect of legacy call to exit(). */
 +    p->shellAbruptExit = 0x100|rc;
    }
 -  return rc;
 +  return DCR_Return;
 +}
 +DISPATCHABLE_COMMAND( quit 1 1 0 ){
 +  /* .quit would be more aptly named .return, as it does nothing more. */
 +  return DCR_Return;
  }
  
 +/*****************
 + * The .expert and .explain commands
 + */
 +CONDITION_COMMAND( expert !defined(SQLITE_OMIT_VIRTUALTABLE) );
 +COLLECT_HELP_TEXT[
 +  ".expert                  Suggest indexes for queries",
 +  ".explain ?on|off|auto?   Change the EXPLAIN formatting mode. Default: auto",
 +];
 +DISPATCHABLE_COMMAND( expert ? 1 1 ){
 +  ShellInState *psi = ISS(p);
 +  int rv = DCR_Ok;
 +  char *zErr = 0;
 +  int i;
 +  int iSample = 0;
  
 -/*
 -** Implementation of .ar "create", "insert", and "update" commands.
 -**
 -**     create    ->     Create a new SQL archive
 -**     insert    ->     Insert or reinsert all files listed
 -**     update    ->     Insert files that have changed or that were not
 -**                      previously in the archive
 -**
 -** Create the "sqlar" table in the database if it does not already exist.
 -** Then add each file in the azFile[] array to the archive. Directories
 -** are added recursively. If argument bVerbose is non-zero, a message is
 -** printed on stdout for each file archived.
 -**
 -** The create command is the same as update, except that it drops
 -** any existing "sqlar" table before beginning.  The "insert" command
 -** always overwrites every file named on the command-line, where as
 -** "update" only overwrites if the size or mtime or mode has changed.
 -*/
 -static int arCreateOrUpdateCommand(
 -  ArCommand *pAr,                 /* Command arguments and options */
 -  int bUpdate,                    /* true for a --create. */
 -  int bOnlyIfChanged              /* Only update if file has changed */
 -){
 -  const char *zCreate =
 -      "CREATE TABLE IF NOT EXISTS sqlar(\n"
 -      "  name TEXT PRIMARY KEY,  -- name of the file\n"
 -      "  mode INT,               -- access permissions\n"
 -      "  mtime INT,              -- last modification time\n"
 -      "  sz INT,                 -- original file size\n"
 -      "  data BLOB               -- compressed content\n"
 -      ")";
 -  const char *zDrop = "DROP TABLE IF EXISTS sqlar";
 -  const char *zInsertFmt[2] = {
 -     "REPLACE INTO %s(name,mode,mtime,sz,data)\n"
 -     "  SELECT\n"
 -     "    %s,\n"
 -     "    mode,\n"
 -     "    mtime,\n"
 -     "    CASE substr(lsmode(mode),1,1)\n"
 -     "      WHEN '-' THEN length(data)\n"
 -     "      WHEN 'd' THEN 0\n"
 -     "      ELSE -1 END,\n"
 -     "    sqlar_compress(data)\n"
 -     "  FROM fsdir(%Q,%Q) AS disk\n"
 -     "  WHERE lsmode(mode) NOT LIKE '?%%'%s;"
 -     ,
 -     "REPLACE INTO %s(name,mode,mtime,data)\n"
 -     "  SELECT\n"
 -     "    %s,\n"
 -     "    mode,\n"
 -     "    mtime,\n"
 -     "    data\n"
 -     "  FROM fsdir(%Q,%Q) AS disk\n"
 -     "  WHERE lsmode(mode) NOT LIKE '?%%'%s;"
 -  };
 -  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];
 -  char *zExists = 0;
 +  if( psi->bSafeMode ) return DCR_AbortError;
 +  assert( psi->expert.pExpert==0 );
 +  memset(&psi->expert, 0, sizeof(ExpertInfo));
  
 -  arExecSql(pAr, "PRAGMA page_size=512");
 -  rc = arExecSql(pAr, "SAVEPOINT ar;");
 -  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";
 +  open_db(p, 0);
 +
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    int n;
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    n = strlen30(z);
 +    if( n>=2 && 0==cli_strncmp(z, "-verbose", n) ){
 +      psi->expert.bVerbose = 1;
 +    }
 +    else if( n>=2 && 0==cli_strncmp(z, "-sample", n) ){
 +      if( i==(nArg-1) ){
 +        return DCR_Unpaired|i;
 +      }else{
 +        iSample = (int)integerValue(azArg[++i]);
 +        if( iSample<0 || iSample>100 ){
 +          *pzErr = smprintf("value out of range: %s\n", azArg[i]);
 +          return DCR_ArgWrong|i;
 +        }
 +      }
      }
 -  }else{
 -    /* Initialize the table for an SQLAR */
 -    zTab = "sqlar";
 -    if( bUpdate==0 ){
 -      rc = arExecSql(pAr, zDrop);
 -      if( rc!=SQLITE_OK ) goto end_ar_transaction;
 +    else{
 +      return DCR_Unknown|i;
      }
 -    rc = arExecSql(pAr, zCreate);
 -  }
 -  if( bOnlyIfChanged ){
 -    zExists = sqlite3_mprintf(
 -      " AND NOT EXISTS("
 -          "SELECT 1 FROM %s AS mem"
 -          " WHERE mem.name=disk.name"
 -          " AND mem.mtime=disk.mtime"
 -          " AND mem.mode=disk.mode)", zTab);
 -  }else{
 -    zExists = sqlite3_mprintf("");
 -  }
 -  if( zExists==0 ) rc = SQLITE_NOMEM;
 -  for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
 -    char *zSql2 = sqlite3_mprintf(zInsertFmt[pAr->bZip], zTab,
 -        pAr->bVerbose ? "shell_putsnl(name)" : "name",
 -        pAr->azArg[i], pAr->zDir, zExists);
 -    rc = arExecSql(pAr, zSql2);
 -    sqlite3_free(zSql2);
    }
 -end_ar_transaction:
 -  if( rc!=SQLITE_OK ){
 -    sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
 +
 +  psi->expert.pExpert = sqlite3_expert_new(DBI(psi), &zErr);
 +  if( psi->expert.pExpert==0 ){
 +    *pzErr = smprintf("sqlite3_expert_new: %s\n",
 +                      zErr ? zErr : "out of memory");
 +    return DCR_Error;
    }else{
 -    rc = arExecSql(pAr, "RELEASE ar;");
 -    if( pAr->bZip && pAr->zFile ){
 -      zSql = sqlite3_mprintf("DROP TABLE %s", zTemp);
 -      arExecSql(pAr, zSql);
 -      sqlite3_free(zSql);
 -    }
 +    sqlite3_expert_config(psi->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample);
    }
 -  sqlite3_free(zExists);
 -  return rc;
 +
 +  return DCR_Ok;
  }
  
 -/*
 -** Implementation of ".ar" dot command.
 -*/
 -static int arDotCommand(
 -  ShellState *pState,          /* Current shell tool state */
 -  int fromCmdLine,             /* True if -A command-line option, not .ar cmd */
 -  char **azArg,                /* Array of arguments passed to dot command */
 -  int nArg                     /* Number of entries in azArg[] */
 -){
 -  ArCommand cmd;
 -  int rc;
 -  memset(&cmd, 0, sizeof(cmd));
 -  cmd.fromCmdLine = fromCmdLine;
 -  rc = arParseCommand(azArg, nArg, &cmd);
 -  if( rc==SQLITE_OK ){
 -    int eDbType = SHELL_OPEN_UNSPEC;
 -    cmd.p = pState;
 -    cmd.db = pState->db;
 -    if( cmd.zFile ){
 -      eDbType = deduceDatabaseType(cmd.zFile, 1);
 +DISPATCHABLE_COMMAND( explain ? 1 2 ){
 +  /* The ".explain" command is automatic now.  It is largely
 +  ** pointless, retained purely for backwards compatibility */
 +  ShellInState *psi = ISS(p);
 +  int val = 1;
 +  if( nArg>1 ){
 +    if( cli_strcmp(azArg[1],"auto")==0 ){
 +      val = 99;
      }else{
 -      eDbType = pState->openMode;
 +      val = booleanValue(azArg[1]);
      }
 -    if( eDbType==SHELL_OPEN_ZIPFILE ){
 -      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 ){
 -      int flags;
 -      if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS;
 -      if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT
 -           || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){
 -        flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
 +  }
 +  if( val==1 && psi->mode!=MODE_Explain ){
 +    psi->normalMode = psi->mode;
 +    psi->mode = MODE_Explain;
 +    psi->autoExplain = 0;
 +  }else if( val==0 ){
 +    if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
 +    psi->autoExplain = 0;
 +  }else if( val==99 ){
 +    if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
 +    psi->autoExplain = 1;
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .excel, .once and .output commands
 + * These share much implementation, so they stick together.
 + */
 +CONDITION_COMMAND(excel !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(once !defined(SQLITE_SHELL_FIDDLE));
 +CONDITION_COMMAND(output !defined(SQLITE_SHELL_FIDDLE));
 +
 +COLLECT_HELP_TEXT[
 +  ".excel                   Display the output of next command in spreadsheet",
 +  "   --bom                   Prefix the file with a UTF8 byte-order mark",
 +  ".once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE",
 +  "   If FILE begins with '|' then open it as a command to be piped into.",
 +  "   Options:",
 +  "     --bom                 Prefix output with a UTF8 byte-order mark",
 +  "     -e                    Send output to the system text editor",
 +  "     -x       Send output as CSV to a spreadsheet (same as \".excel\")",
 +  ".output ?FILE?           Send output to FILE or stdout if FILE is omitted",
 +  "   If FILE begins with '|' then open it as a command to be piped into.",
 +  "   Options:",
 +  "     --bom                 Prefix output with a UTF8 byte-order mark",
 +  "     -e                    Send output to the system text editor",
 +  "     -x       Send output as CSV to a spreadsheet (same as \".excel\")",
 +];
 +#ifndef SQLITE_SHELL_FIDDLE
 +/* Shared implementation of .excel, .once and .output */
 +static DotCmdRC outputRedirs(char *azArg[], int nArg,
 +                                      ShellInState *psi, char **pzErr,
 +                                      int bOnce, int eMode){
 +  /* bOnce => 0: .output, 1: .once, 2: .excel */
 +  /* eMode => 'x' for excel, else 0 */
 +  int rc = 0;
 +  char *zFile = 0;
 +  u8 bTxtMode = 0;
 +  u8 bPutBOM = 0;
 +  int i;
 +  static unsigned const char zBOM[4] = {0xef,0xbb,0xbf,0};
 +  if( psi->bSafeMode ) return DCR_AbortError;
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      if( z[1]=='-' ) z++;
 +      if( cli_strcmp(z,"-bom")==0 ){
 +        bPutBOM = 1;
 +      }else if( bOnce!=2 && cli_strcmp(z,"-x")==0 ){
 +        eMode = 'x';  /* spreadsheet */
 +      }else if( bOnce!=2 && cli_strcmp(z,"-e")==0 ){
 +        eMode = 'e';  /* text editor */
        }else{
 -        flags = SQLITE_OPEN_READONLY;
 -      }
 -      cmd.db = 0;
 -      if( cmd.bDryRun ){
 -        utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile,
 -             eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
 -      }
 -      rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags,
 -             eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0);
 -      if( rc!=SQLITE_OK ){
 -        utf8_printf(stderr, "cannot open file: %s (%s)\n",
 -            cmd.zFile, sqlite3_errmsg(cmd.db)
 -        );
 -        goto end_ar_command;
 +        return DCR_Unknown|i;
 +      }
 +    }else if( zFile==0 && eMode!='e' && eMode!='x' ){
 +      zFile = smprintf("%s", z);
 +      shell_check_oom(zFile);
 +      if( zFile[0]=='|' ){
 +        while( i+1<nArg ){
 +          zFile = smprintf("%z %s", zFile, azArg[++i]);
 +          shell_check_oom(zFile);
 +        }
 +        break;
        }
 -      sqlite3_fileio_init(cmd.db, 0, 0);
 -      sqlite3_sqlar_init(cmd.db, 0, 0);
 -      sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p,
 -                              shellPutsFunc, 0, 0);
 -
 +    }else{
 +      sqlite3_free(zFile);
 +      return DCR_TooMany|i;
      }
 -    if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){
 -      if( cmd.eCmd!=AR_CMD_CREATE
 -       && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0)
 -      ){
 -        utf8_printf(stderr, "database does not contain an 'sqlar' table\n");
 -        rc = SQLITE_ERROR;
 -        goto end_ar_command;
 +  }
 +  if( zFile==0 ){
 +    zFile = smprintf("stdout");
 +    shell_check_oom(zFile);
 +  }
 +  if( bOnce ){
 +    psi->outCount = 2;
 +  }else{
 +    psi->outCount = 0;
 +  }
 +  output_reset(psi);
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  if( eMode=='e' || eMode=='x' ){
 +    psi->doXdgOpen = 1;
 +    outputModePush(psi);
 +    if( eMode=='x' ){
 +      /* spreadsheet mode.  Output as CSV. */
 +      newTempFile(psi, "csv");
 +      psi->shellFlgs &= ~SHFLG_Echo;
 +      psi->mode = MODE_Csv;
 +      sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, SEP_Comma);
 +      sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_CrLf);
 +    }else{
 +      /* text editor mode */
 +      newTempFile(psi, "txt");
 +      bTxtMode = 1;
 +    }
 +    sqlite3_free(zFile);
 +    zFile = smprintf("%s", psi->zTempFile);
 +  }
 +#endif /* SQLITE_NOHAVE_SYSTEM */
 +  shell_check_oom(zFile);
 +  if( zFile[0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = smprintf("pipes are not supported in this OS\n");
 +    rc = 1;
 +    psi->out = STD_OUT;
 +#else
 +    psi->out = popen(zFile + 1, "w");
 +    if( psi->out==0 ){
 +      *pzErr = smprintf("cannot open pipe \"%s\"\n", zFile + 1);
 +      psi->out = STD_OUT;
 +      rc = 1;
 +    }else{
 +      if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
 +      sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
 +    }
 +#endif
 +  }else{
 +    psi->out = output_file_open(zFile, bTxtMode);
 +    if( psi->out==0 ){
 +      if( cli_strcmp(zFile,"off")!=0 ){
 +        *pzErr = smprintf("cannot write to \"%s\"\n", zFile);
        }
 -      cmd.zSrcTable = sqlite3_mprintf("sqlar");
 +      psi->out = STD_OUT;
 +      rc = 1;
 +    } else {
 +      if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
 +      sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
      }
 +  }
 +  sqlite3_free(zFile);
 +  return DCR_Ok|rc;
 +}
 +#endif /* !defined(SQLITE_SHELL_FIDDLE)*/
  
 -    switch( cmd.eCmd ){
 -      case AR_CMD_CREATE:
 -        rc = arCreateOrUpdateCommand(&cmd, 0, 0);
 -        break;
 +DISPATCHABLE_COMMAND( excel ? 1 2 ){
 +  return outputRedirs(azArg, nArg, ISS(p), pzErr, 2, 'x');
 +}
 +DISPATCHABLE_COMMAND( once ? 1 6 ){
 +  return outputRedirs(azArg, nArg, ISS(p), pzErr, 1, 0);
 +}
 +DISPATCHABLE_COMMAND( output ? 1 6 ){
 +  return outputRedirs(azArg, nArg, ISS(p), pzErr, 0, 0);
 +}
  
 -      case AR_CMD_EXTRACT:
 -        rc = arExtractCommand(&cmd);
 -        break;
  
 -      case AR_CMD_LIST:
 -        rc = arListCommand(&cmd);
 -        break;
 +/*****************
 + * The .filectrl and fullschema commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".filectrl CMD ...        Run various sqlite3_file_control() operations",
 +  "   --schema SCHEMA         Use SCHEMA instead of \"main\"",
 +  "   --help                  Show CMD details",
 +  ".fullschema ?--indent?   Show schema and the content of sqlite_stat tables",
 +];
 +DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
 +  static const struct {
 +    const char *zCtrlName;   /* Name of a test-control option */
 +    int ctrlCode;            /* Integer code for that option */
 +    const char *zUsage;      /* Usage notes */
 +  } aCtrl[] = {
 +    { "chunk_size",     SQLITE_FCNTL_CHUNK_SIZE,      "SIZE"           },
 +    { "data_version",   SQLITE_FCNTL_DATA_VERSION,    ""               },
 +    { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },
 +    { "lock_timeout",   SQLITE_FCNTL_LOCK_TIMEOUT,    "MILLISEC"       },
 +    { "persist_wal",    SQLITE_FCNTL_PERSIST_WAL,     "[BOOLEAN]"      },
 + /* { "pragma",         SQLITE_FCNTL_PRAGMA,          "NAME ARG"       },*/
 +    { "psow",       SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]"      },
 +    { "reserve_bytes",  SQLITE_FCNTL_RESERVE_BYTES,   "[N]"            },
 +    { "size_limit",     SQLITE_FCNTL_SIZE_LIMIT,      "[LIMIT]"        },
 +    { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
 + /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
 +  };
 +  ShellInState *psi = ISS(p);
 +  int filectrl = -1;
 +  int iCtrl = -1;
 +  sqlite3_int64 iRes = 0;  /* Integer result to display if rc2==1 */
 +  int isOk = 0;            /* 0: usage  1: %lld  2: no-result */
 +  int n2, i;
 +  const char *zCmd = 0;
 +  const char *zSchema = 0;
  
 -      case AR_CMD_HELP:
 -        arUsage(pState->out);
 -        break;
 +  open_db(p, 0);
 +  zCmd = nArg>=2 ? azArg[1] : "help";
  
 -      case AR_CMD_INSERT:
 -        rc = arCreateOrUpdateCommand(&cmd, 1, 0);
 -        break;
 +  if( zCmd[0]=='-'
 +      && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0)
 +      && nArg>=4
 +      ){
 +    zSchema = azArg[2];
 +    for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
 +    nArg -= 2;
 +    zCmd = azArg[1];
 +  }
  
 -      case AR_CMD_REMOVE:
 -        rc = arRemoveCommand(&cmd);
 -        break;
 +  /* The argument can optionally begin with "-" or "--" */
 +  if( zCmd[0]=='-' && zCmd[1] ){
 +    zCmd++;
 +    if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +  }
  
 -      default:
 -        assert( cmd.eCmd==AR_CMD_UPDATE );
 -        rc = arCreateOrUpdateCommand(&cmd, 1, 1);
 -        break;
 +  /* --help lists all file-controls */
 +  if( cli_strcmp(zCmd,"help")==0 ){
 +    utf8_printf(psi->out, "Available file-controls:\n");
 +    for(i=0; i<ArraySize(aCtrl); i++){
 +      utf8_printf(psi->out, "  .filectrl %s %s\n",
 +                  aCtrl[i].zCtrlName, aCtrl[i].zUsage);
      }
 +    return DCR_Error;
    }
 -end_ar_command:
 -  if( cmd.db!=pState->db ){
 -    close_db(cmd.db);
 -  }
 -  sqlite3_free(cmd.zSrcTable);
 -
 -  return rc;
 -}
 -/* End of the ".archive" or ".ar" command logic
 -*******************************************************************************/
 -#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */
 -
 -#if SQLITE_SHELL_HAVE_RECOVER
 -
 -/*
 -** This function is used as a callback by the recover extension. Simply
 -** print the supplied SQL statement to stdout.
 -*/
 -static int recoverSqlCb(void *pCtx, const char *zSql){
 -  ShellState *pState = (ShellState*)pCtx;
 -  utf8_printf(pState->out, "%s;\n", zSql);
 -  return SQLITE_OK;
 -}
 -
 -/*
 -** This function is called to recover data from the database. A script
 -** to construct a new database containing all recovered data is output
 -** on stream pState->out.
 -*/
 -static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
 -  int rc = SQLITE_OK;
 -  const char *zRecoveryDb = "";   /* Name of "recovery" database.  Debug only */
 -  const char *zLAF = "lost_and_found";
 -  int bFreelist = 1;              /* 0 if --ignore-freelist is specified */
 -  int bRowids = 1;                /* 0 if --no-rowids */
 -  sqlite3_recover *p = 0;
 -  int i = 0;
  
 -  for(i=1; i<nArg; i++){
 -    char *z = azArg[i];
 -    int n;
 -    if( z[0]=='-' && z[1]=='-' ) z++;
 -    n = strlen30(z);
 -    if( n<=17 && memcmp("-ignore-freelist", z, n)==0 ){
 -      bFreelist = 0;
 -    }else
 -    if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
 -      /* This option determines the name of the ATTACH-ed database used
 -      ** internally by the recovery extension.  The default is "" which
 -      ** means to use a temporary database that is automatically deleted
 -      ** when closed.  This option is undocumented and might disappear at
 -      ** any moment. */
 -      i++;
 -      zRecoveryDb = azArg[i];
 -    }else
 -    if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
 -      i++;
 -      zLAF = azArg[i];
 -    }else
 -    if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
 -      bRowids = 0;
 +  /* Convert filectrl text option to value. Allow any
 +  ** unique prefix of the option name, or a numerical value. */
 +  n2 = strlen30(zCmd);
 +  for(i=0; i<ArraySize(aCtrl); i++){
 +    if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 +      if( filectrl<0 ){
 +        filectrl = aCtrl[i].ctrlCode;
 +        iCtrl = i;
 +      }else{
 +        *pzErr = smprintf("ambiguous file-control: \"%s\"\n"
 +                          "Use \".filectrl --help\" for help\n", zCmd);
 +        return DCR_ArgWrong;
 +      }
      }
 -    else{
 -      utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
 -      showHelp(pState->out, azArg[0]);
 -      return 1;
 +  }
 +  if( filectrl<0 ){
 +    *pzErr = smprintf("unknown file-control: %s\n"
 +                      "Use \".filectrl --help\" for help\n", zCmd);
 +    return DCR_ArgWrong;
 +  }else{
 +   switch(filectrl){
 +    case SQLITE_FCNTL_SIZE_LIMIT: {
 +      if( nArg!=2 && nArg!=3 ) break;
 +      iRes = nArg==3 ? integerValue(azArg[2]) : -1;
 +      sqlite3_file_control(DBX(p), zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
 +      isOk = 1;
 +      break;
 +    }
 +    case SQLITE_FCNTL_LOCK_TIMEOUT:
 +    case SQLITE_FCNTL_CHUNK_SIZE: {
 +      int x;
 +      if( nArg!=3 ) break;
 +      x = (int)integerValue(azArg[2]);
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      isOk = 2;
 +      break;
 +    }
 +    case SQLITE_FCNTL_PERSIST_WAL:
 +    case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
 +      int x;
 +      if( nArg!=2 && nArg!=3 ) break;
 +      x = nArg==3 ? booleanValue(azArg[2]) : -1;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      iRes = x;
 +      isOk = 1;
 +      break;
 +    }
 +    case SQLITE_FCNTL_DATA_VERSION:
 +    case SQLITE_FCNTL_HAS_MOVED: {
 +      int x;
 +      if( nArg!=2 ) break;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      iRes = x;
 +      isOk = 1;
 +      break;
      }
 +    case SQLITE_FCNTL_TEMPFILENAME: {
 +      char *z = 0;
 +      if( nArg!=2 ) break;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &z);
 +      if( z ){
 +        utf8_printf(psi->out, "%s\n", z);
 +        sqlite3_free(z);
 +      }
 +      isOk = 2;
 +      break;
 +    }
 +    case SQLITE_FCNTL_RESERVE_BYTES: {
 +      int x;
 +      if( nArg>=3 ){
 +        x = atoi(azArg[2]);
 +        sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      }
 +      x = -1;
 +      sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
 +      utf8_printf(psi->out,"%d\n", x);
 +      isOk = 2;
 +      break;
 +    }
 +   }
 +  }
 +  if( isOk==0 && iCtrl>=0 ){
 +    utf8_printf(psi->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 +    return DCR_CmdErred;
 +  }else if( isOk==1 ){
 +    char zBuf[100];
 +    sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
 +    raw_printf(psi->out, "%s\n", zBuf);
 +  }
 +  return DCR_Ok;
 +}
 +
 +DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
 +  int rc;
 +  ShellInState data;
 +  ShellExState datax;
 +  int doStats = 0;
 +  /* Consider some refactoring to avoid this wholesale copying. */
 +  memcpy(&data, ISS(p), sizeof(data));
 +  memcpy(&datax, p, sizeof(datax));
 +  data.pSXS = &datax;
 +  datax.pSIS = &data;
 +  data.showHeader = 0;
 +  data.cMode = data.mode = MODE_Semi;
 +  if( nArg==2 && optionMatch(azArg[1], "indent") ){
 +    data.cMode = data.mode = MODE_Pretty;
 +    nArg = 1;
 +  }
 +  if( nArg!=1 ){
 +    return DCR_TooMany|1;
 +  }
 +  open_db(p, 0);
 +  rc = sqlite3_exec(datax.dbUser,
 +    "SELECT sql FROM"
 +    "  (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
 +    "     FROM sqlite_schema UNION ALL"
 +    "   SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
 +    "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
 +    "ORDER BY x",
 +    callback, &datax, 0
 +  );
 +  if( rc==SQLITE_OK ){
 +    sqlite3_stmt *pStmt;
 +    rc = sqlite3_prepare_v2(datax.dbUser,
 +                            "SELECT rowid FROM sqlite_schema"
 +                            " WHERE name GLOB 'sqlite_stat[134]'",
 +                            -1, &pStmt, 0);
 +    doStats = sqlite3_step(pStmt)==SQLITE_ROW;
 +    sqlite3_finalize(pStmt);
    }
 +  if( doStats==0 ){
 +    raw_printf(data.out, "/* No STAT tables available */\n");
 +  }else{
 +    raw_printf(data.out, "ANALYZE sqlite_schema;\n");
 +    data.cMode = data.mode = MODE_Insert;
 +    datax.zDestTable = "sqlite_stat1";
 +    shell_exec(&datax, "SELECT * FROM sqlite_stat1", 0);
 +    datax.zDestTable = "sqlite_stat4";
 +    shell_exec(&datax, "SELECT * FROM sqlite_stat4", 0);
 +    raw_printf(data.out, "ANALYZE sqlite_schema;\n");
 +  }
 +  return rc > 0;
 +}
  
 -  p = sqlite3_recover_init_sql(
 -      pState->db, "main", recoverSqlCb, (void*)pState
 -  );
 +/*****************
 + * The .headers command
 + */
 +COLLECT_HELP_TEXT[
 +  ".headers on|off          Turn display of headers on or off",
 +];
 +DISPATCHABLE_COMMAND( headers 6 2 2 ){
 +  ISS(p)->showHeader = booleanValue(azArg[1]);
 +  ISS(p)->shellFlgs |= SHFLG_HeaderSet;
 +  return DCR_Ok;
 +}
  
 -  sqlite3_recover_config(p, 789, (void*)zRecoveryDb);  /* Debug use only */
 -  sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF);
 -  sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
 -  sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
 +/*****************
 + * The .help command
 + */
  
 -  sqlite3_recover_run(p);
 -  if( sqlite3_recover_errcode(p)!=SQLITE_OK ){
 -    const char *zErr = sqlite3_recover_errmsg(p);
 -    int errCode = sqlite3_recover_errcode(p);
 -    raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode);
 +/* This literal's value AND address are used for help's workings. */
 +static const char *zHelpAll = "-all";
 +
 +COLLECT_HELP_TEXT[
 +  ".help ?PATTERN?|?-all?   Show help for PATTERN or everything, or summarize",
 +  "                           Repeat -all to see undocumented commands",
 +];
 +DISPATCHABLE_COMMAND( help 3 1 3 ){
 +  const char *zPat = 0;
 +  FILE *out = ISS(p)->out;
 +  if( nArg>1 ){
 +    char *z = azArg[1];
 +    if( nArg==3 && cli_strcmp(z, zHelpAll)==0 && cli_strcmp(azArg[2], zHelpAll)==0 ){
 +      /* Show the undocumented command help */
 +      zPat = zHelpAll;
 +    }else if( cli_strcmp(z,"-a")==0 || optionMatch(z, "all") ){
 +      zPat = "";
 +    }else{
 +      zPat = z;
 +    }
    }
 -  rc = sqlite3_recover_finish(p);
 -  return rc;
 +  if( showHelp(out, zPat, p)==0 && nArg>1 ){
 +    utf8_printf(out, "Nothing matches '%s'\n", azArg[1]);
 +  }
 +  /* Help pleas never fail! */
 +  return DCR_Ok;
  }
 -#endif /* SQLITE_SHELL_HAVE_RECOVER */
  
 -
 -/*
 - * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
 - * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
 - *   close db and set it to 0, and return the columns spec, to later
 - *   be sqlite3_free()'ed by the caller.
 - * The return is 0 when either:
 - *   (a) The db was not initialized and zCol==0 (There are no columns.)
 - *   (b) zCol!=0  (Column was added, db initialized as needed.)
 - * The 3rd argument, pRenamed, references an out parameter. If the
 - * pointer is non-zero, its referent will be set to a summary of renames
 - * done if renaming was necessary, or set to 0 if none was done. The out
 - * string (if any) must be sqlite3_free()'ed by the caller.
 +CONDITION_COMMAND(import !defined(SQLITE_SHELL_FIDDLE));
 +/*****************
 + * The .import command
   */
 -#ifdef SHELL_DEBUG
 -#define rc_err_oom_die(rc) \
 -  if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
 -  else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
 -    fprintf(stderr,"E:%d\n",rc), assert(0)
 +COLLECT_HELP_TEXT[
 +  ".import FILE TABLE       Import data from FILE into TABLE",
 +  "   Options:",
 +  "     --ascii               Use \\037 and \\036 as column and row separators",
 +  "     --csv                 Use , and \\n as column and row separators",
 +  "     --skip N              Skip the first N rows of input",
 +  "     --schema S            Target table to be S.TABLE",
 +  "     -v                    \"Verbose\" - increase auxiliary output",
 +  "   Notes:",
 +  "     *  If TABLE does not exist, it is created.  The first row of input",
 +  "        determines the column names.",
 +  "     *  If neither --csv or --ascii are used, the input mode is derived",
 +  "        from the \".mode\" output mode",
 +  "     *  If FILE begins with \"|\" then it is a command that generates the",
 +  "        input text.",
 +];
 +DISPATCHABLE_COMMAND( import ? 3 7 ){
 +  char *zTable = 0;           /* Insert data into this table */
 +  char *zSchema = 0;          /* within this schema (may default to "main") */
 +  char *zFile = 0;            /* Name of file to extra content from */
 +  sqlite3_stmt *pStmt = NULL; /* A statement */
 +  int nCol;                   /* Number of columns in the table */
 +  int nByte;                  /* Number of bytes in an SQL string */
 +  int i, j;                   /* Loop counters */
 +  int needCommit;             /* True to COMMIT or ROLLBACK at end */
 +  int nSep;                   /* Number of bytes in psi->colSeparator[] */
 +  char *zSql;                 /* An SQL statement */
 +  char *zFullTabName;         /* Table name with schema if applicable */
 +  ImportCtx sCtx;             /* Reader context */
 +  char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
 +  int eVerbose = 0;           /* Larger for more console output */
 +  int nSkip = 0;              /* Initial lines to skip */
 +  int useOutputMode = 1;      /* Use output mode to determine separators */
 +  FILE *out = ISS(p)->out;    /* output stream */
 +  char *zCreate = 0;          /* CREATE TABLE statement text */
 +  ShellInState *psi = ISS(p);
 +  int rc = 0;
 +
 +  if(psi->bSafeMode) return DCR_AbortError;
 +  memset(&sCtx, 0, sizeof(sCtx));
 +  if( psi->mode==MODE_Ascii ){
 +    xRead = ascii_read_one_field;
 +  }else{
 +    xRead = csv_read_one_field;
 +  }
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    if( z[0]!='-' ){
 +      if( zFile==0 ){
 +        zFile = z;
 +      }else if( zTable==0 ){
 +        zTable = z;
 +      }else{
 +        return DCR_TooMany|i;
 +      }
 +    }else if( cli_strcmp(z,"-v")==0 ){
 +      eVerbose++;
 +    }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
 +      zSchema = azArg[++i];
 +    }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
 +      nSkip = integerValue(azArg[++i]);
 +    }else if( cli_strcmp(z,"-ascii")==0 ){
 +      sCtx.cColSep = SEP_Unit[0];
 +      sCtx.cRowSep = SEP_Record[0];
 +      xRead = ascii_read_one_field;
 +      useOutputMode = 0;
 +    }else if( cli_strcmp(z,"-csv")==0 ){
 +      sCtx.cColSep = ',';
 +      sCtx.cRowSep = '\n';
 +      xRead = csv_read_one_field;
 +      useOutputMode = 0;
 +    }else{
 +      return DCR_Unknown|i;
 +    }
 +  }
 +  if( zTable==0 ){
 +    *pzErr = smprintf("missing %s argument.\n", zFile==0 ? "FILE" : "TABLE");
 +    return DCR_Missing;
 +  }
 +  seenInterrupt = 0;
 +  open_db(p, 0);
 +  if( useOutputMode ){
 +    const char *zYap = 0;
 +    /* If neither the --csv or --ascii options are specified, then set
 +    ** the column and row separator characters from the output mode. */
 +    nSep = strlen30(psi->colSeparator);
 +    if( nSep==0 ){
 +      zYap = "non-null column separator required for import";
 +    }
 +    if( nSep>1 ){
 +      zYap = "multi-character or multi-byte column separators"
 +        " not allowed for import";
 +    }
 +    nSep = strlen30(psi->rowSeparator);
 +    if( nSep==0 ){
 +      zYap = "non-null row separator required for import";
 +    }
 +    if( zYap!=0 ){
 +      *pzErr = smprintf("%s\n", zYap);
 +      return DCR_Error;
 +    }
 +    if( nSep==2 && psi->mode==MODE_Csv
 +        && cli_strcmp(psi->rowSeparator,SEP_CrLf)==0 ){
 +      /* When importing CSV (only), if the row separator is set to the
 +      ** default output row separator, change it to the default input
 +      ** row separator.  This avoids having to maintain different input
 +      ** and output row separators. */
 +      sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_Row);
 +      nSep = strlen30(psi->rowSeparator);
 +    }
 +    if( nSep>1 ){
 +      *pzErr
 +        = smprintf("multi-character row separators not allowed for import\n");
 +      return DCR_Error;
 +    }
-     sCtx.cColSep = psi->colSeparator[0];
-     sCtx.cRowSep = psi->rowSeparator[0];
++    sCtx.cColSep = (u8)psi->colSeparator[0];
++    sCtx.cRowSep = (u8)psi->rowSeparator[0];
 +  }
 +  sCtx.zFile = zFile;
 +  sCtx.nLine = 1;
 +  if( sCtx.zFile[0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = smprintf("pipes are not supported in this OS\n");
 +    return DCR_Error;
  #else
 -static void rc_err_oom_die(int rc){
 -  if( rc==SQLITE_NOMEM ) shell_check_oom(0);
 -  assert(rc==SQLITE_OK||rc==SQLITE_DONE);
 -}
 +    sCtx.in = popen(sCtx.zFile+1, "r");
 +    sCtx.zFile = "<pipe>";
 +    sCtx.xCloser = pclose;
  #endif
 +  }else{
 +    sCtx.in = fopen(sCtx.zFile, "rb");
 +    sCtx.xCloser = fclose;
 +  }
 +  if( sCtx.in==0 ){
 +    *pzErr = smprintf("cannot open \"%s\"\n", zFile);
 +    return DCR_Error;
 +  }
 +  sCtx.z = sqlite3_malloc64(120);
 +  if( sCtx.z==0 ){
 +    import_cleanup(&sCtx);
 +    shell_out_of_memory();
 +  }
 +  /* Here and below, resources must be freed before exit. */
 +  if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
 +    char zSep[2];
 +    zSep[1] = 0;
 +    zSep[0] = sCtx.cColSep;
 +    utf8_printf(out, "Column separator ");
 +    output_c_string(out, zSep);
 +    utf8_printf(out, ", row separator ");
 +    zSep[0] = sCtx.cRowSep;
 +    output_c_string(out, zSep);
 +    utf8_printf(out, "\n");
 +  }
 +  while( (nSkip--)>0 ){
 +    while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
 +  }
 +  if( zSchema!=0 ){
 +    zFullTabName = smprintf("\"%w\".\"%w\"", zSchema, zTable);
 +  }else{
 +    zFullTabName = smprintf("\"%w\"", zTable);
 +  }
 +  zSql = smprintf("SELECT * FROM %s", zFullTabName);
 +  if( zSql==0 || zFullTabName==0 ){
 +    import_cleanup(&sCtx);
 +    shell_out_of_memory();
 +  }
 +  nByte = strlen30(zSql);
 +  rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
 +  import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
 +  if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(DBX(p)))==0 ){
 +    zCreate = smprintf("CREATE TABLE %s", zFullTabName);
 +    sqlite3 *dbCols = 0;
 +    char *zRenames = 0;
 +    char *zColDefs;
 +    while( xRead(&sCtx) ){
 +      zAutoColumn(sCtx.z, &dbCols, 0);
 +      if( sCtx.cTerm!=sCtx.cColSep ) break;
 +    }
 +    zColDefs = zAutoColumn(0, &dbCols, &zRenames);
 +    if( zRenames!=0 ){
 +      FILE *fh = INSOURCE_IS_INTERACTIVE(psi->pInSource)?  out : STD_ERR;
 +      utf8_printf(fh, "Columns renamed during .import %s due to duplicates:\n"
 +                  "%s\n", sCtx.zFile, zRenames);
 +      sqlite3_free(zRenames);
 +    }
 +    assert(dbCols==0);
 +    if( zColDefs==0 ){
 +      *pzErr = smprintf("%s: empty file\n", sCtx.zFile);
 +      sqlite3_free(zCreate);
 +    import_fail: /* entry from outer blocks */
 +      sqlite3_free(zSql);
 +      sqlite3_free(zFullTabName);
 +      import_cleanup(&sCtx);
 +      return DCR_Error;
 +    }
 +    zCreate = smprintf("%z%z\n", zCreate, zColDefs);
 +    if( eVerbose>=1 ){
 +      utf8_printf(out, "%s\n", zCreate);
 +    }
 +    rc = sqlite3_exec(DBX(p), zCreate, 0, 0, 0);
 +    sqlite3_free(zCreate);
 +    if( rc ){
 +      *pzErr = smprintf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p)));
 +      goto import_fail;
 +    }
 +    rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
 +  }
 +  if( rc ){
 +    if (pStmt) sqlite3_finalize(pStmt);
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    goto import_fail;
 +  }
 +  sqlite3_free(zSql);
 +  nCol = sqlite3_column_count(pStmt);
 +  sqlite3_finalize(pStmt);
 +  pStmt = 0;
 +  if( nCol==0 ) return DCR_Ok; /* no columns, no error */
 +  zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
 +  if( zSql==0 ){
 +    import_cleanup(&sCtx);
 +    shell_out_of_memory();
 +  }
 +  sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
 +  j = strlen30(zSql);
 +  for(i=1; i<nCol; i++){
 +    zSql[j++] = ',';
 +    zSql[j++] = '?';
 +  }
 +  zSql[j++] = ')';
 +  zSql[j] = 0;
 +  if( eVerbose>=2 ){
 +    utf8_printf(psi->out, "Insert using: %s\n", zSql);
 +  }
 +  rc = sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
 +  if( rc ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    if (pStmt) sqlite3_finalize(pStmt);
 +    goto import_fail;
 +  }
 +  sqlite3_free(zSql);
 +  sqlite3_free(zFullTabName);
 +  needCommit = sqlite3_get_autocommit(DBX(p));
 +  if( needCommit ) sqlite3_exec(DBX(p), "BEGIN", 0, 0, 0);
 +  do{
 +    int startLine = sCtx.nLine;
 +    for(i=0; i<nCol; i++){
 +      char *z = xRead(&sCtx);
 +      /*
 +      ** Did we reach end-of-file before finding any columns?
 +      ** If so, stop instead of NULL filling the remaining columns.
 +      */
 +      if( z==0 && i==0 ) break;
 +      /*
 +      ** Did we reach end-of-file OR end-of-line before finding any
 +      ** columns in ASCII mode?  If so, stop instead of NULL filling
 +      ** the remaining columns.
 +      */
 +      if( psi->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
 +      sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
 +      if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
 +        utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
 +                    "filling the rest with NULL\n",
 +                    sCtx.zFile, startLine, nCol, i+1);
 +        i += 2;
 +        while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
 +      }
 +    }
 +    if( sCtx.cTerm==sCtx.cColSep ){
 +      do{
 +        xRead(&sCtx);
 +        i++;
 +      }while( sCtx.cTerm==sCtx.cColSep );
 +      utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
 +                  "extras ignored\n",
 +                  sCtx.zFile, startLine, nCol, i);
 +    }
 +    if( i>=nCol ){
 +      sqlite3_step(pStmt);
 +      rc = sqlite3_reset(pStmt);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
 +                    startLine, sqlite3_errmsg(DBX(p)));
 +        sCtx.nErr++;
 +      }else{
 +        sCtx.nRow++;
 +      }
 +    }
 +  }while( sCtx.cTerm!=EOF );
  
 -#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
 -static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
 -#else  /* Otherwise, memory is faster/better for the transient DB. */
 -static const char *zCOL_DB = ":memory:";
 -#endif
 +  import_cleanup(&sCtx);
 +  sqlite3_finalize(pStmt);
 +  if( needCommit ) sqlite3_exec(DBX(p), "COMMIT", 0, 0, 0);
 +  if( eVerbose>0 ){
 +    utf8_printf(out,
 +      "Added %d rows with %d errors using %d lines of input\n",
 +      sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
 +  }
 +  return DCR_Ok|(sCtx.nErr>0);
 +}
  
 -/* Define character (as C string) to separate generated column ordinal
 - * from protected part of incoming column names. This defaults to "_"
 - * so that incoming column identifiers that did not need not be quoted
 - * remain usable without being quoted. It must be one character.
 +/*****************
 + * The .keyword command
   */
 -#ifndef SHELL_AUTOCOLUMN_SEP
 -# define AUTOCOLUMN_SEP "_"
 -#else
 -# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
 -#endif
 -
 -static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
 -  /* Queries and D{D,M}L used here */
 -  static const char * const zTabMake = "\
 -CREATE TABLE ColNames(\
 - cpos INTEGER PRIMARY KEY,\
 - name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
 -CREATE VIEW RepeatedNames AS \
 -SELECT DISTINCT t.name FROM ColNames t \
 -WHERE t.name COLLATE NOCASE IN (\
 - SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
 -);\
 -";
 -  static const char * const zTabFill = "\
 -INSERT INTO ColNames(name,nlen,chop,reps,suff)\
 - VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
 -";
 -  static const char * const zHasDupes = "\
 -SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
 - <count(name) FROM ColNames\
 -";
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -  static const char * const zDedoctor = "\
 -UPDATE ColNames SET chop=iif(\
 -  (substring(name,nlen,1) BETWEEN '0' AND '9')\
 -  AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
 - nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
 - 0\
 -)\
 -";
 -#endif
 -  static const char * const zSetReps = "\
 -UPDATE ColNames AS t SET reps=\
 -(SELECT count(*) FROM ColNames d \
 - WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
 - COLLATE NOCASE\
 -)\
 -";
 -#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
 -  static const char * const zColDigits = "\
 -SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
 -";
 -#else
 -  /* Counting on SQLITE_MAX_COLUMN < 100,000 here. (32767 is the hard limit.) */
 -  static const char * const zColDigits = "\
 -SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \
 - WHEN (nc < 1000) THEN 3 WHEN (nc < 10000) THEN 4 \
 - ELSE 5 FROM (SELECT count(*) AS nc FROM ColNames) \
 -";
 -#endif
 -  static const char * const zRenameRank =
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -    "UPDATE ColNames AS t SET suff="
 -    "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
 -#else /* ...RENAME_MINIMAL_ONE_PASS */
 -"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
 -"  SELECT 0 AS nlz"
 -"  UNION"
 -"  SELECT nlz+1 AS nlz FROM Lzn"
 -"  WHERE EXISTS("
 -"   SELECT 1"
 -"   FROM ColNames t, ColNames o"
 -"   WHERE"
 -"    iif(t.name IN (SELECT * FROM RepeatedNames),"
 -"     printf('%s"AUTOCOLUMN_SEP"%s',"
 -"      t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
 -"     t.name"
 -"    )"
 -"    ="
 -"    iif(o.name IN (SELECT * FROM RepeatedNames),"
 -"     printf('%s"AUTOCOLUMN_SEP"%s',"
 -"      o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
 -"     o.name"
 -"    )"
 -"    COLLATE NOCASE"
 -"    AND o.cpos<>t.cpos"
 -"   GROUP BY t.cpos"
 -"  )"
 -") UPDATE Colnames AS t SET"
 -" chop = 0," /* No chopping, never touch incoming names. */
 -" suff = iif(name IN (SELECT * FROM RepeatedNames),"
 -"  printf('"AUTOCOLUMN_SEP"%s', substring("
 -"   printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
 -"  ''"
 -" )"
 -#endif
 -    ;
 -  static const char * const zCollectVar = "\
 -SELECT\
 - '('||x'0a'\
 - || group_concat(\
 -  cname||' TEXT',\
 -  ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
 - ||')' AS ColsSpec \
 -FROM (\
 - SELECT cpos, printf('\"%w\"',printf('%!.*s%s', nlen-chop,name,suff)) AS cname \
 - FROM ColNames ORDER BY cpos\
 -)";
 -  static const char * const zRenamesDone =
 -    "SELECT group_concat("
 -    " printf('\"%w\" to \"%w\"',name,printf('%!.*s%s', nlen-chop, name, suff)),"
 -    " ','||x'0a')"
 -    "FROM ColNames WHERE suff<>'' OR chop!=0"
 -    ;
 -  int rc;
 -  sqlite3_stmt *pStmt = 0;
 -  assert(pDb!=0);
 -  if( zColNew ){
 -    /* Add initial or additional column. Init db if necessary. */
 -    if( *pDb==0 ){
 -      if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
 -#ifdef SHELL_COLFIX_DB
 -      if(*zCOL_DB!=':')
 -        sqlite3_exec(*pDb,"drop table if exists ColNames;"
 -                     "drop view if exists RepeatedNames;",0,0,0);
 -#endif
 -      rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
 -      rc_err_oom_die(rc);
 +CONDITION_COMMAND( keyword !defined(NO_KEYWORD_COMMAND) );
 +COLLECT_HELP_TEXT[
 +  ".keyword ?KW?            List keywords, or say whether KW is one.",
 +];
 +DISPATCHABLE_COMMAND( keyword ? 1 2 ){
 +  FILE *out = ISS(p)->out;
 +  if( nArg<2 ){
 +    int i = 0;
 +    int nk = sqlite3_keyword_count();
 +    int nCol = 0;
 +    int szKW;
 +    while( i<nk ){
 +      const char *zKW = 0;
 +      if( SQLITE_OK==sqlite3_keyword_name(i++, &zKW, &szKW) ){
 +        char kwBuf[50];
 +        if( szKW < sizeof(kwBuf) ){
 +          const char *zSep = " ";
 +          if( (nCol += (1+szKW))>75){
 +            zSep = "\n";
 +            nCol = 0;
 +          }
 +          memcpy(kwBuf, zKW, szKW);
 +          kwBuf[szKW] = 0;
 +          utf8_printf(out, "%s%s", kwBuf, zSep);
 +        }
 +      }
      }
 -    assert(*pDb!=0);
 -    rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_step(pStmt);
 -    rc_err_oom_die(rc);
 -    sqlite3_finalize(pStmt);
 -    return 0;
 -  }else if( *pDb==0 ){
 -    return 0;
 +    if( nCol>0 ) utf8_printf(out, "\n");
    }else{
 -    /* Formulate the columns spec, close the DB, zero *pDb. */
 -    char *zColsSpec = 0;
 -    int hasDupes = db_int(*pDb, zHasDupes);
 -    int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
 -    if( hasDupes ){
 -#ifdef SHELL_COLUMN_RENAME_CLEAN
 -      rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
 -      rc_err_oom_die(rc);
 +    int szKW = strlen30(azArg[1]);
 +    int isKeyword = sqlite3_keyword_check(azArg[1], szKW);
 +    utf8_printf(out, "%s is%s a keyword\n",
 +                azArg[1], (isKeyword)? "" : " not");
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .imposter, .iotrace, .limit, .lint and .log commands
 + */
 +CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) );
 +CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) );
 +CONDITION_COMMAND( load !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE));
- CONDITION_COMMAND(log !defined(SQLITE_SHELL_FIDDLE));
 +COLLECT_HELP_TEXT[
-   ".imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
-   ".iotrace FILE            Enable I/O diagnostic logging to FILE",
++  ",imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
++  ",iotrace FILE            Enable I/O diagnostic logging to FILE",
 +  ".limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT",
 +  ".lint OPTIONS            Report potential schema issues.",
 +  "     Options:",
 +  "        fkey-indexes     Find missing foreign key indexes",
-   ".log FILE|off            Turn logging on or off.  FILE can be stderr/stdout",
++#if !defined(SQLITE_SHELL_FIDDLE)
++  ".log FILE|on|off         Turn logging on or off.  FILE can be stderr/stdout",
++#else
++  ".log on|off              Turn logging on or off.",
+ #endif
 -      rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
 -      rc_err_oom_die(rc);
 -      rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
 -      rc_err_oom_die(rc);
 -      sqlite3_bind_int(pStmt, 1, nDigits);
 -      rc = sqlite3_step(pStmt);
 -      sqlite3_finalize(pStmt);
 -      if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM);
 +];
 +DISPATCHABLE_COMMAND( imposter ? 3 3 ){
 +  int rc = 0;
 +  char *zSql;
 +  char *zCollist = 0;
 +  sqlite3_stmt *pStmt;
 +  sqlite3 *db;
 +  int tnum = 0;
 +  int isWO = 0;  /* True if making an imposter of a WITHOUT ROWID table */
 +  int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
 +  int i;
++  if( !ShellHasFlag(p,SHFLG_TestingMode) ){
++    utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
++                "imposter");
++    return DCR_Error;
++  }
 +  if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
 +    *pzErr = smprintf("Usage: .imposter INDEX IMPOSTER\n"
 +                      "       .imposter off\n");
 +    /* Also allowed, but not documented:
 +    **
 +    **    .imposter TABLE IMPOSTER
 +    **
 +    ** where TABLE is a WITHOUT ROWID table.  In that case, the
 +    ** imposter is another WITHOUT ROWID table with the columns in
 +    ** storage order. */
 +    return DCR_SayUsage;
 +  }
 +  open_db(p, 0);
 +  db = DBX(p);
 +  if( nArg==2 ){
 +    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 1);
 +    return DCR_Ok;
 +  }
 +  zSql = smprintf("SELECT rootpage, 0 FROM sqlite_schema"
 +                  " WHERE name='%q' AND type='index'"
 +                  "UNION ALL "
 +                  "SELECT rootpage, 1 FROM sqlite_schema"
 +                  " WHERE name='%q' AND type='table'"
 +                  "  AND sql LIKE '%%without%%rowid%%'",
 +                  azArg[1], azArg[1]);
 +  sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +  sqlite3_free(zSql);
 +  if( sqlite3_step(pStmt)==SQLITE_ROW ){
 +    tnum = sqlite3_column_int(pStmt, 0);
 +    isWO = sqlite3_column_int(pStmt, 1);
 +  }
 +  sqlite3_finalize(pStmt);
 +  zSql = smprintf("PRAGMA index_xinfo='%q'", azArg[1]);
 +  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +  sqlite3_free(zSql);
 +  i = 0;
 +  while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +    char zLabel[20];
 +    const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
 +    i++;
 +    if( zCol==0 ){
 +      if( sqlite3_column_int(pStmt,1)==-1 ){
 +        zCol = "_ROWID_";
 +      }else{
 +        sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
 +        zCol = zLabel;
 +      }
      }
 -    assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */
 -    rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_step(pStmt);
 -    if( rc==SQLITE_ROW ){
 -      zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 +    if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
 +      lenPK = (int)strlen(zCollist);
 +    }
 +    if( zCollist==0 ){
 +      zCollist = smprintf("\"%w\"", zCol);
      }else{
 -      zColsSpec = 0;
 +      zCollist = smprintf("%z,\"%w\"", zCollist, zCol);
      }
 -    if( pzRenamed!=0 ){
 -      if( !hasDupes ) *pzRenamed = 0;
 -      else{
 -        sqlite3_finalize(pStmt);
 -        if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
 -            && SQLITE_ROW==sqlite3_step(pStmt) ){
 -          *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 -        }else
 -          *pzRenamed = 0;
 +  }
 +  sqlite3_finalize(pStmt);
 +  if( i==0 || tnum==0 ){
 +    *pzErr = smprintf("no such index: \"%s\"\n", azArg[1]);
 +    sqlite3_free(zCollist);
 +    return DCR_Error;
 +  }
 +  if( lenPK==0 ) lenPK = 100000;
 +  zSql = smprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))"
 +                  "WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist);
 +  sqlite3_free(zCollist);
 +  rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum);
 +  if( rc==SQLITE_OK ){
 +    rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +    sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 0);
 +    if( rc ){
 +      *pzErr = smprintf("Error in [%s]: %s\n", zSql, sqlite3_errmsg(db));
 +    }else{
 +      utf8_printf(STD_OUT, "%s;\n", zSql);
 +      raw_printf(STD_OUT, "WARNING: "
 +                 "writing to an imposter table will corrupt the \"%s\" %s!\n",
 +                 azArg[1], isWO ? "table" : "index"
 +                 );
 +    }
 +  }else{
 +    *pzErr = smprintf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 +  }
 +  sqlite3_free(zSql);
 +  return rc != 0;
 +}
 +DISPATCHABLE_COMMAND( iotrace ? 2 2 ){
 +  SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
 +  if( iotrace && iotrace!=STD_OUT ) fclose(iotrace);
 +  iotrace = 0;
 +  if( nArg<2 ){
 +    sqlite3IoTrace = 0;
 +  }else if( cli_strcmp(azArg[1], "-")==0 ){
 +    sqlite3IoTrace = iotracePrintf;
 +    iotrace = STD_OUT;
 +  }else{
 +    iotrace = fopen(azArg[1], "w");
 +    if( iotrace==0 ){
 +      *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
 +      sqlite3IoTrace = 0;
 +      return DCR_Error;
 +    }else{
 +      sqlite3IoTrace = iotracePrintf;
 +    }
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .limits and .load commands
 + */
 +COLLECT_HELP_TEXT[
 +  ",limits ?LIMIT_NAME?     Display limit selected by its name, or all limits",
 +  ".load FILE ?ENTRY?       Load a SQLite extension library",
 +  "   If ENTRY is provided, the entry point \"sqlite_ENTRY_init\" is called.",
 +  "   Otherwise, the entry point name is derived from the FILE's name.",
 +];
 +
 +DISPATCHABLE_COMMAND( limits 5 1 3 ){
 +  static const struct {
 +    const char *zLimitName;   /* Name of a limit */
 +    int limitCode;            /* Integer code for that limit */
 +  } aLimit[] = {
 +    { "length",                SQLITE_LIMIT_LENGTH                    },
 +    { "sql_length",            SQLITE_LIMIT_SQL_LENGTH                },
 +    { "column",                SQLITE_LIMIT_COLUMN                    },
 +    { "expr_depth",            SQLITE_LIMIT_EXPR_DEPTH                },
 +    { "compound_select",       SQLITE_LIMIT_COMPOUND_SELECT           },
 +    { "vdbe_op",               SQLITE_LIMIT_VDBE_OP                   },
 +    { "function_arg",          SQLITE_LIMIT_FUNCTION_ARG              },
 +    { "attached",              SQLITE_LIMIT_ATTACHED                  },
 +    { "like_pattern_length",   SQLITE_LIMIT_LIKE_PATTERN_LENGTH       },
 +    { "variable_number",       SQLITE_LIMIT_VARIABLE_NUMBER           },
 +    { "trigger_depth",         SQLITE_LIMIT_TRIGGER_DEPTH             },
 +    { "worker_threads",        SQLITE_LIMIT_WORKER_THREADS            },
 +  };
 +  int i, n2;
 +  open_db(p, 0);
 +  if( nArg==1 ){
 +    for(i=0; i<ArraySize(aLimit); i++){
 +      fprintf(STD_OUT, "%20s %d\n", aLimit[i].zLimitName,
 +             sqlite3_limit(DBX(p), aLimit[i].limitCode, -1));
 +    }
 +  }else if( nArg>3 ){
 +    return DCR_TooMany;
 +  }else{
 +    int iLimit = -1;
 +    n2 = strlen30(azArg[1]);
 +    for(i=0; i<ArraySize(aLimit); i++){
 +      if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
 +        if( iLimit<0 ){
 +          iLimit = i;
 +        }else{
 +          *pzErr = smprintf("ambiguous limit: \"%s\"\n", azArg[1]);
 +          return DCR_Error;
 +        }
        }
      }
 -    sqlite3_finalize(pStmt);
 -    sqlite3_close(*pDb);
 -    *pDb = 0;
 -    return zColsSpec;
 +    if( iLimit<0 ){
 +      *pzErr = smprintf("unknown limit: \"%s\"\n"
 +                        "enter \".limits\" with no arguments for a list.\n",
 +                        azArg[1]);
 +      return DCR_ArgWrong;
 +    }
 +    if( nArg==3 ){
 +      sqlite3_limit(DBX(p), aLimit[iLimit].limitCode,
 +                    (int)integerValue(azArg[2]));
 +    }
 +    fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName,
 +           sqlite3_limit(DBX(p), aLimit[iLimit].limitCode, -1));
 +  }
 +  return DCR_Ok;
 +}
 +
 +DISPATCHABLE_COMMAND( lint 3 1 0 ){
 +  sqlite3 *db;                    /* Database handle to query "main" db of */
 +  FILE *out = ISS(p)->out;        /* Stream to write non-error output to */
 +  int bVerbose = 0;               /* If -verbose is present */
 +  int bGroupByParent = 0;         /* If -groupbyparent is present */
 +  int i;                          /* To iterate through azArg[] */
 +  const char *zIndent = "";       /* How much to indent CREATE INDEX by */
 +  int rc;                         /* Return code */
 +  sqlite3_stmt *pSql = 0;         /* Compiled version of SQL statement below */
 +
 +  i = (nArg>=2 ? strlen30(azArg[1]) : 0);
 +  if( i==0 || 0!=sqlite3_strnicmp(azArg[1], "fkey-indexes", i) ){
 +    *pzErr = smprintf
 +      ("Usage %s sub-command ?switches...?\n"
 +       "Where sub-commands are:\n"
 +       "    fkey-indexes\n", azArg[0]);
 +    return DCR_SayUsage;
 +  }
 +  open_db(p, 0);
 +  db = DBX(p);
 +
 +  /*
 +  ** This SELECT statement returns one row for each foreign key constraint
 +  ** in the schema of the main database. The column values are:
 +  **
 +  ** 0. The text of an SQL statement similar to:
 +  **
 +  **      "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
 +  **
 +  **    This SELECT is similar to the one that the foreign keys implementation
 +  **    needs to run internally on child tables. If there is an index that can
 +  **    be used to optimize this query, then it can also be used by the FK
 +  **    implementation to optimize DELETE or UPDATE statements on the parent
 +  **    table.
 +  **
 +  ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
 +  **    the EXPLAIN QUERY PLAN command matches this pattern, then the schema
 +  **    contains an index that can be used to optimize the query.
 +  **
 +  ** 2. Human readable text that describes the child table and columns. e.g.
 +  **
 +  **       "child_table(child_key1, child_key2)"
 +  **
 +  ** 3. Human readable text that describes the parent table and columns. e.g.
 +  **
 +  **       "parent_table(parent_key1, parent_key2)"
 +  **
 +  ** 4. A full CREATE INDEX statement for an index that could be used to
 +  **    optimize DELETE or UPDATE statements on the parent table. e.g.
 +  **
 +  **       "CREATE INDEX child_table_child_key ON child_table(child_key)"
 +  **
 +  ** 5. The name of the parent table.
 +  **
 +  ** These six values are used by the C logic below to generate the report.
 +  */
 +  const char *zSql =
 +  "SELECT "
 +    "     'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
 +    "  || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
 +    "  || fkey_collate_clause("
 +    "       f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
 +    ", "
 +    "     'SEARCH ' || s.name || ' USING COVERING INDEX*('"
 +    "  || group_concat('*=?', ' AND ') || ')'"
 +    ", "
 +    "     s.name  || '(' || group_concat(f.[from],  ', ') || ')'"
 +    ", "
 +    "     f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
 +    ", "
 +    "     'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
 +    "  || ' ON ' || quote(s.name) || '('"
 +    "  || group_concat(quote(f.[from]) ||"
 +    "        fkey_collate_clause("
 +    "          f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
 +    "  || ');'"
 +    ", "
 +    "     f.[table] "
 +    "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
 +    "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
 +    "GROUP BY s.name, f.id "
 +    "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
 +  ;
 +  const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
 +
 +  for(i=2; i<nArg; i++){
 +    int n = strlen30(azArg[i]);
 +    if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
 +      bVerbose = 1;
 +    }
 +    else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
 +      bGroupByParent = 1;
 +      zIndent = "    ";
 +    }
 +    else{
 +      raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
 +                 azArg[0], azArg[1]
 +      );
 +      return DCR_Unknown|i;
 +    }
 +  }
 +
 +  /* Register the fkey_collate_clause() SQL function */
 +  rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
 +      0, shellFkeyCollateClause, 0, 0
 +  );
 +
 +  if( rc==SQLITE_OK ){
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0);
 +  }
 +  if( rc==SQLITE_OK ){
 +    sqlite3_bind_int(pSql, 1, bGroupByParent);
 +  }
 +
 +  if( rc==SQLITE_OK ){
 +    int rc2;
 +    char *zPrev = 0;
 +    while( SQLITE_ROW==sqlite3_step(pSql) ){
 +      int res = -1;
 +      sqlite3_stmt *pExplain = 0;
 +      const char *zEQP = (const char*)sqlite3_column_text(pSql, 0);
 +      const char *zGlob = (const char*)sqlite3_column_text(pSql, 1);
 +      const char *zFrom = (const char*)sqlite3_column_text(pSql, 2);
 +      const char *zTarget = (const char*)sqlite3_column_text(pSql, 3);
 +      const char *zCI = (const char*)sqlite3_column_text(pSql, 4);
 +      const char *zParent = (const char*)sqlite3_column_text(pSql, 5);
 +
 +      if( zEQP==0 || zGlob==0 ) continue;
 +      rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
 +      if( rc!=SQLITE_OK ) break;
 +      if( SQLITE_ROW==sqlite3_step(pExplain) ){
 +        const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3);
 +        res = zPlan!=0 && (  0==sqlite3_strglob(zGlob, zPlan)
 +                             || 0==sqlite3_strglob(zGlobIPK, zPlan));
 +      }
 +      rc = sqlite3_finalize(pExplain);
 +      if( rc!=SQLITE_OK ) break;
 +
 +      if( res<0 ){
 +        raw_printf(STD_ERR, "Error: internal error");
 +        break;
 +      }else{
 +        if( bGroupByParent
 +        && (bVerbose || res==0)
 +        && (zPrev==0 || sqlite3_stricmp(zParent, zPrev))
 +        ){
 +          raw_printf(out, "-- Parent table %s\n", zParent);
 +          sqlite3_free(zPrev);
 +          zPrev = smprintf("%s", zParent);
 +        }
 +
 +        if( res==0 ){
 +          raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget);
 +        }else if( bVerbose ){
 +          raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n",
 +                     zIndent, zFrom, zTarget);
 +        }
 +      }
 +    }
 +    sqlite3_free(zPrev);
 +
 +    if( rc!=SQLITE_OK ){
 +      *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 +    }
 +
 +    rc2 = sqlite3_finalize(pSql);
 +    if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
 +      rc = rc2;
 +      *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
 +    }
 +  }else{
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
    }
 +
 +  return DCR_Ok|(rc!=0);
  }
  
 -/*
 -** If an input line begins with "." then invoke this routine to
 -** process that line.
 -**
 -** Return 1 on error, 2 to exit, and 0 otherwise.
 -*/
 -static int do_meta_command(char *zLine, ShellState *p){
 -  int h = 1;
 -  int nArg = 0;
 -  int n, c;
 -  int rc = 0;
 -  char *azArg[52];
 +DISPATCHABLE_COMMAND( load ? 2 3 ){
 +  const char *zFile = 0, *zProc = 0;
 +  int ai = 1, rc;
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
++  if( nArg<2 || azArg[1][0]==0 ){
++    /* Must have a non-empty FILE. (Will not load self.) */
++    return DCR_SayUsage;
++  }
 +  while( ai<nArg ){
 +    const char *zA = azArg[ai++];
 +    if( zFile==0 ) zFile = zA;
 +    else if( zProc==0 ) zProc = zA;
 +    else return DCR_TooMany|ai;
 +  }
 +  open_db(p, 0);
 +  rc = sqlite3_load_extension(DBX(p), zFile, zProc, pzErr);
 +  return DCR_Ok|(rc!=SQLITE_OK);
 +}
  
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( p->expert.pExpert ){
 -    expertFinish(p, 1, 0);
 +DISPATCHABLE_COMMAND( log ? 2 2 ){
 +  const char *zFile = azArg[1];
-   if( ISS(p)->bSafeMode ) return DCR_AbortError;
++  int bOn = cli_strcmp(zFile,"on")==0;
++  int bOff = cli_strcmp(zFile,"off")==0;
++  if( ISS(p)->bSafeMode && !bOn && !bOff ){
++    return DCR_AbortError;
+   }
 +  output_file_close(ISS(p)->pLog);
++  if( bOff ){
++    ISS(p)->pLog = 0;
++    return DCR_Ok;
++  }
++#if defined(SQLITE_SHELL_FIDDLE)
++  if( !bOn ) return DCR_SayUsage;
++#else
++  if( bOn ) zFile = "stdout";
+ #endif
 +  ISS(p)->pLog = output_file_open(zFile, 0);
 +  return DCR_Ok;
 +}
  
 -  /* Parse the input line into tokens.
 -  */
 -  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
 -    while( IsSpace(zLine[h]) ){ h++; }
 -    if( zLine[h]==0 ) break;
 -    if( zLine[h]=='\'' || zLine[h]=='"' ){
 -      int delim = zLine[h++];
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && zLine[h]!=delim ){
 -        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
 -        h++;
 -      }
 -      if( zLine[h]==delim ){
 -        zLine[h++] = 0;
 -      }
 -      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
 -    }else{
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
 -      if( zLine[h] ) zLine[h++] = 0;
 -      resolve_backslashes(azArg[nArg-1]);
 +static void effectMode(ShellInState *psi, u8 modeRequest, u8 modeNominal){
 +  /* Effect the specified mode change. */
 +  const char *zColSep = 0, *zRowSep = 0;
 +  assert(modeNominal!=MODE_COUNT_OF);
 +  switch( modeRequest ){
 +  case MODE_Line:
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Column:
 +    if( (psi->shellFlgs & SHFLG_HeaderSet)==0 ){
 +      psi->showHeader = 1;
      }
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_List:
 +    zColSep = SEP_Column;
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Html:
 +    break;
 +  case MODE_Tcl:
 +    zColSep = SEP_Space;
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Csv:
 +    zColSep = SEP_Comma;
 +    zRowSep = SEP_CrLf;
 +    break;
 +  case MODE_Tab:
 +    zColSep = SEP_Tab;
 +    break;
 +  case MODE_Insert:
 +    break;
 +  case MODE_Quote:
 +    zColSep = SEP_Comma;
 +    zRowSep = SEP_Row;
 +    break;
 +  case MODE_Ascii:
 +    zColSep = SEP_Unit;
 +    zRowSep = SEP_Record;
 +    break;
 +  case MODE_Markdown:
 +    /* fall-thru */
 +  case MODE_Table:
 +    /* fall-thru */
 +  case MODE_Box:
 +    break;
 +  case MODE_Count:
 +    /* fall-thru */
 +  case MODE_Off:
 +    /* fall-thru */
 +  case MODE_Json:
 +    break;
 +  case MODE_Explain: case MODE_Semi: case MODE_EQP: case MODE_Pretty: default:
 +    /* Modes used internally, not settable by .mode command. */
 +    return;
    }
 -  azArg[nArg] = 0;
 -
 -  /* Process the input line.
 -  */
 -  if( nArg==0 ) return 0; /* no tokens, no error */
 -  n = strlen30(azArg[0]);
 -  c = azArg[0][0];
 -  clearTempFile(p);
 +  if( zRowSep!=0 ){
 +    sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, zRowSep);
 +  }
 +  if( zColSep!=0 ){
 +    sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, zColSep);
 +  }
 +  psi->mode = modeNominal;
 +}
  
 -#ifndef SQLITE_OMIT_AUTHORIZATION
 -  if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .auth ON|OFF\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    if( booleanValue(azArg[1]) ){
 -      sqlite3_set_authorizer(p->db, shellAuth, p);
 -    }else if( p->bSafeModePersist ){
 -      sqlite3_set_authorizer(p->db, safeModeAuth, p);
 +/*****************
 + * The .mode command
 + */
 +COLLECT_HELP_TEXT[
 +  ".mode MODE ?OPTIONS?     Set output mode",
 +  "   MODE is one of:",
 +  "     ascii       Columns/rows delimited by 0x1F and 0x1E",
 +  "     box         Tables using unicode box-drawing characters",
 +  "     csv         Comma-separated values",
 +  "     column      Output in columns.  (See .width)",
 +  "     html        HTML <table> code",
 +  "     insert      SQL insert statements for TABLE",
 +  "     json        Results in a JSON array",
 +  "     line        One value per line",
 +  "     list        Values delimited by \"|\"",
 +  "     markdown    Markdown table format",
 +  "     qbox        Shorthand for \"box --wrap 60 --quote\"",
 +  "     quote       Escape answers as for SQL",
 +  "     table       ASCII-art table",
 +  "     tabs        Tab-separated values",
 +  "     tcl         TCL list elements",
 +  "   OPTIONS: (for columnar modes or insert mode):",
 +  "     --wrap N       Wrap output lines to no longer than N characters",
 +  "     --wordwrap B   Wrap or not at word boundaries per B (on/off)",
 +  "     --ww           Shorthand for \"--wordwrap 1\"",
 +  "     --quote        Quote output text as SQL literals",
 +  "     --noquote      Do not quote output text",
 +  "     TABLE          The name of SQL table used for \"insert\" mode",
 +];
 +DISPATCHABLE_COMMAND( mode ? 1 0 ){
 +  ShellInState *psi = ISS(p);
 +  const char *zTabname = 0;
 +  const char *zArg;
 +  int i, aix;
 +  u8 foundMode = MODE_COUNT_OF, setMode = MODE_COUNT_OF;
 +  ColModeOpts cmOpts = ColModeOpts_default;
 +  for(aix=1; aix<nArg; aix++){
 +    zArg = azArg[aix];
 +    if( optionMatch(zArg,"wrap") && aix+1<nArg ){
 +      cmOpts.iWrap = integerValue(azArg[++aix]);
 +    }else if( optionMatch(zArg,"ww") ){
 +      cmOpts.bWordWrap = 1;
 +    }else if( optionMatch(zArg,"wordwrap") && aix+1<nArg ){
 +      cmOpts.bWordWrap = (u8)booleanValue(azArg[++aix]);
 +    }else if( optionMatch(zArg,"quote") ){
 +      cmOpts.bQuote = 1;
 +    }else if( optionMatch(zArg,"noquote") ){
 +      cmOpts.bQuote = 0;
 +    }else{
 +      /* Not a known option. Check for known mode, or possibly a table name. */
 +      if( foundMode==MODE_Insert && zTabname==0 ){
 +        zTabname = zArg;
 +      }else if( *zArg=='-' ){
 +        goto flag_unknown;
 +      }else{
 +        int im, nza = strlen30(zArg);
 +        if( foundMode!=MODE_COUNT_OF ) goto mode_badarg;
 +        for( im=0; im<MODE_COUNT_OF; ++im ){
 +          if( modeDescr[im].bUserBlocked ) continue;
 +          if( cli_strncmp(zArg,modeDescr[im].zModeName,nza)==0 ){
 +            foundMode = (u8)im;
 +            setMode = modeDescr[im].iAliasFor;
 +            break;
 +          }
 +        }
 +        if( cli_strcmp(zArg, "qbox")==0 ){
 +          ColModeOpts cmo = ColModeOpts_default_qbox;
 +          foundMode = setMode = MODE_Box;
 +          cmOpts = cmo;
 +        }else if( im==MODE_COUNT_OF ) goto mode_unknown;
 +      }
 +    }
 +  } /* Arg loop */
 +  if( foundMode==MODE_COUNT_OF ){
 +    FILE *out = psi->out;
 +    const char *zMode;
 +    int nms;
 +    i = psi->mode;
 +    assert(i>=0 && i<MODE_COUNT_OF);
 +    zMode = modeDescr[i].zModeName;
 +    nms = strlen30(zMode)-modeDescr[i].bDepluralize;
 +    /* Mode not specified. Show present mode (and toss any options set.) */
 +    if( MODE_IS_COLUMNAR(psi->mode) ){
 +      raw_printf
 +        (out, "current output mode: %.*s --wrap %d --wordwrap %s --%squote\n",
 +         nms, zMode, psi->cmOpts.iWrap,
 +         psi->cmOpts.bWordWrap ? "on" : "off",
 +         psi->cmOpts.bQuote ? "" : "no");
      }else{
 -      sqlite3_set_authorizer(p->db, 0, 0);
 +      raw_printf(out, "current output mode: %.*s\n", nms, zMode);
      }
 -  }else
 +  }else{
 +    effectMode(psi, foundMode, setMode);
 +    if( MODE_IS_COLUMNAR(setMode) ){
 +      psi->cmOpts = cmOpts;
 +#if SHELL_DATAIO_EXT
 +      psi->pActiveExporter = psi->pColumnarExporter;
  #endif
 -
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) \
 -  && !defined(SQLITE_SHELL_FIDDLE)
 -  if( c=='a' && cli_strncmp(azArg[0], "archive", n)==0 ){
 -    open_db(p, 0);
 -    failIfSafeMode(p, "cannot run .archive in safe mode");
 -    rc = arDotCommand(p, 0, azArg, nArg);
 -  }else
 +    }else{
 +#if SHELL_DATAIO_EXT
 +      psi->pActiveExporter = psi->pFreeformExporter;
  #endif
 +      if( setMode==MODE_Insert ){
 +        set_table_name(p, zTabname ? zTabname : "table");
 +      }
 +    }
 +  }
 +  psi->cMode = psi->mode;
 +  return DCR_Ok;
 + flag_unknown:
 +  *pzErr = smprintf("Unknown .mode option: %s\nValid options:\n%s",
 +                    zArg,
 +                    "  --noquote\n"
 +                    "  --quote\n"
 +                    "  --wordwrap on/off\n"
 +                    "  --wrap N\n"
 +                    "  --ww\n");
 +  return DCR_Unknown|aix;
 + mode_unknown:
 +  *pzErr = smprintf("Mode should be one of:\n"
 +                    "  ascii box column csv html insert json line\n"
 +                    "  list markdown qbox quote table tabs tcl\n");
 +  return DCR_Unknown|aix;
 + mode_badarg:
 +  *pzErr = smprintf("Invalid .mode argument: %s\n", zArg);
 +  return DCR_ArgWrong|aix;
 +}
  
 +/* Note that .open is (partially) available in WASM builds but is
 +** currently only intended to be used by the fiddle tool, not
 +** end users, so is "undocumented." */
 +#ifdef SQLITE_SHELL_FIDDLE
 +# define HOPEN ",open"
 +#else
 +# define HOPEN ".open"
 +#endif
 +/* ToDo: Get defined help text collection into macro processor. */
 +/*****************
 + * The .nonce, .nullvalue and .open commands
 + */
 +CONDITION_COMMAND(nonce !defined(SQLITE_SHELL_FIDDLE));
 +COLLECT_HELP_TEXT[
 +  ".open ?OPTIONS? ?FILE?   Close existing database and reopen FILE",
 +  "     Options:",
 +  "        --append        Use appendvfs to append database to the end of FILE",
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +  "        --deserialize   Load into memory using sqlite3_deserialize()",
 +  "        --hexdb         Load the output of \"dbtotxt\" as an in-memory db",
 +  "        --maxsize N     Maximum size for --hexdb or --deserialized database",
 +#endif
 +  "        --new           Initialize FILE to an empty database",
 +  "        --nofollow      Do not follow symbolic links",
 +  "        --readonly      Open FILE readonly",
 +  "        --zip           FILE is a ZIP archive",
 +  ".nonce STRING            Suspend safe mode for one command if nonce matches",
 +  ".nullvalue STRING        Use STRING in place of NULL values",
 +];
 +DISPATCHABLE_COMMAND( open 3 1 0 ){
 +  ShellInState *psi = ISS(p);
 +  const char *zFN = 0;     /* Pointer to constant filename */
 +  char *zNewFilename = 0;  /* Name of the database file to open */
 +  int iName = 1;           /* Index in azArg[] of the filename */
 +  int newFlag = 0;         /* True to delete file before opening */
 +  u8 openMode = SHELL_OPEN_UNSPEC;
 +  int openFlags = 0;
 +  sqlite3_int64 szMax = 0;
 +  int rc = 0;
 +  /* Check for command-line arguments */
 +  for(iName=1; iName<nArg; iName++){
 +    const char *z = azArg[iName];
  #ifndef SQLITE_SHELL_FIDDLE
 -  if( (c=='b' && n>=3 && cli_strncmp(azArg[0], "backup", n)==0)
 -   || (c=='s' && n>=3 && cli_strncmp(azArg[0], "save", n)==0)
 -  ){
 -    const char *zDestFile = 0;
 -    const char *zDb = 0;
 -    sqlite3 *pDest;
 -    sqlite3_backup *pBackup;
 -    int j;
 -    int bAsync = 0;
 -    const char *zVfs = 0;
 -    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 -    for(j=1; j<nArg; j++){
 -      const char *z = azArg[j];
 -      if( z[0]=='-' ){
 -        if( z[1]=='-' ) z++;
 -        if( cli_strcmp(z, "-append")==0 ){
 -          zVfs = "apndvfs";
 -        }else
 -        if( cli_strcmp(z, "-async")==0 ){
 -          bAsync = 1;
 -        }else
 -        {
 -          utf8_printf(stderr, "unknown option: %s\n", azArg[j]);
 -          return 1;
 -        }
 -      }else if( zDestFile==0 ){
 -        zDestFile = azArg[j];
 -      }else if( zDb==0 ){
 -        zDb = zDestFile;
 -        zDestFile = azArg[j];
 -      }else{
 -        raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n");
 -        return 1;
 -      }
 -    }
 -    if( zDestFile==0 ){
 -      raw_printf(stderr, "missing FILENAME argument on .backup\n");
 -      return 1;
 +    if( optionMatch(z,"new") ){
 +      newFlag = 1;
 +# ifdef SQLITE_HAVE_ZLIB
 +    }else if( optionMatch(z, "zip") ){
 +      openMode = SHELL_OPEN_ZIPFILE;
 +# endif
 +    }else if( optionMatch(z, "append") ){
 +      openMode = SHELL_OPEN_APPENDVFS;
 +    }else if( optionMatch(z, "readonly") ){
 +      openMode = SHELL_OPEN_READONLY;
 +    }else if( optionMatch(z, "nofollow") ){
 +      openFlags |= SQLITE_OPEN_NOFOLLOW;
 +# ifndef SQLITE_OMIT_DESERIALIZE
 +    }else if( optionMatch(z, "deserialize") ){
 +      openMode = SHELL_OPEN_DESERIALIZE;
 +    }else if( optionMatch(z, "hexdb") ){
 +      openMode = SHELL_OPEN_HEXDB;
 +    }else if( optionMatch(z, "maxsize") && iName+1<nArg ){
 +      szMax = integerValue(azArg[++iName]);
 +# endif /* SQLITE_OMIT_DESERIALIZE */
 +    }else
 +#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +    if( z[0]=='-' ){
 +      return DCR_Unknown|iName;
 +    }else if( zFN ){
 +      *pzErr = smprintf("extra argument: \"%s\"\n", z);
 +      return DCR_TooMany;
 +    }else{
 +      zFN = z;
      }
 -    if( zDb==0 ) zDb = "main";
 -    rc = sqlite3_open_v2(zDestFile, &pDest,
 -                  SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile);
 -      close_db(pDest);
 -      return 1;
 +  }
 +
 +  /* Close the existing database */
 +  session_close_all(psi, -1);
 +#if SHELL_DYNAMIC_EXTENSION
 +  if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbAboutToClose, DBX(p));
 +#endif
 +  close_db(DBX(p));
 +  DBX(p) = 0;
 +  psi->pAuxDb->zDbFilename = 0;
 +  sqlite3_free(psi->pAuxDb->zFreeOnClose);
 +  psi->pAuxDb->zFreeOnClose = 0;
 +  psi->openMode = openMode;
 +  psi->openFlags = 0;
 +  psi->szMax = 0;
 +
 +  /* If a filename is specified, try to open it first */
 +  if( zFN || psi->openMode==SHELL_OPEN_HEXDB ){
 +    if( newFlag && zFN && !psi->bSafeMode ) shellDeleteFile(zFN);
 +#ifndef SQLITE_SHELL_FIDDLE
 +    if( psi->bSafeMode
 +        && psi->openMode!=SHELL_OPEN_HEXDB
 +        && zFN
 +        && cli_strcmp(zFN,":memory:")!=0
 +        ){
 +      *pzErr = smprintf("cannot open database files in safe mode");
 +      return DCR_AbortError;
      }
 -    if( bAsync ){
 -      sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
 -                   0, 0, 0);
 +#else
 +      /* WASM mode has its own sandboxed pseudo-filesystem. */
 +#endif
 +    if( zFN ){
 +      zNewFilename = smprintf("%s", zFN);
 +      shell_check_oom(zNewFilename);
 +    }else{
 +      zNewFilename = 0;
 +    }
 +    psi->pAuxDb->zDbFilename = zNewFilename;
 +    psi->openFlags = openFlags;
 +    psi->szMax = szMax;
 +    open_db(p, OPEN_DB_KEEPALIVE);
 +    if( DBX(p)==0 ){
 +      *pzErr = smprintf("cannot open '%s'\n", zNewFilename);
 +      sqlite3_free(zNewFilename);
 +      rc = 1;
 +    }else{
 +      psi->pAuxDb->zFreeOnClose = zNewFilename;
      }
 +  }
 +  if( DBX(p)==0 ){
 +    /* As a fall-back open a TEMP database */
 +    psi->pAuxDb->zDbFilename = 0;
      open_db(p, 0);
 -    pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
 -    if( pBackup==0 ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
 -      close_db(pDest);
 -      return 1;
 +  }
 +  return DCR_Ok|(rc!=0);
 +}
 +
 +DISPATCHABLE_COMMAND( nonce ? 2 2 ){
 +  ShellInState *psi = ISS(p);
 +  if( psi->zNonce==0 || cli_strcmp(azArg[1],psi->zNonce)!=0 ){
 +    raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n",
 +               psi->pInSource->lineno, azArg[1]);
 +    exit(1);
 +  }
 +  /* Suspend safe mode for 1 dot-command after this. */
 +  psi->bSafeModeFuture = 2;
 +  return DCR_Ok;
 +}
 +
 +DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){
 +  sqlite3_snprintf(sizeof(ISS(p)->nullValue), ISS(p)->nullValue, "%.*s",
 +                   (int)ArraySize(ISS(p)->nullValue)-1, azArg[1]);
 +  return DCR_Ok;
 +}
 +
 +/* Helper functions for .parameter and .vars commands
 + */
 +
 +struct keyval_row { char * value; int uses; int hits; };
 +
 +static int kv_find_callback(void *pData, int nc, char **pV, char **pC){
 +  assert(nc>=1);
 +  assert(cli_strcmp(pC[0],"value")==0);
 +  struct keyval_row *pParam = (struct keyval_row *)pData;
 +  assert(pParam->value==0); /* key values are supposedly unique. */
 +  if( pParam->value!=0 ) sqlite3_free( pParam->value );
 +  pParam->value = smprintf("%s", pV[0]); /* source owned by statement */
 +  if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
 +  ++pParam->hits;
 +  return 0;
 +}
 +
 +static void append_in_clause(sqlite3_str *pStr,
 +                             const char **azBeg, const char **azLim);
 +static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
 +                              const char **azBeg, const char **azLim);
 +static char *find_home_dir(int clearFlag);
 +
 +/* Create a home-relative pathname from ~ prefixed path.
 + * Return it, or 0 for any error.
 + * Caller must sqlite3_free() it.
 + */
 +static char *home_based_path( const char *zPath ){
 +  char *zHome = find_home_dir(0);
 +  char *zErr = 0;
 +  assert( zPath[0]=='~' );
 +  if( zHome==0 ){
 +    zErr = "Cannot find home directory.";
 +  }else if( zPath[0]==0 || (zPath[1]!='/'
 +#if defined(_WIN32) || defined(WIN32)
 +                            && zPath[1]!='\\'
 +#endif
 +                            ) ){
 +    zErr = "Malformed pathname";
 +  }else{
 +    return smprintf("%s%s", zHome, zPath+1);
 +  }
 +  utf8_printf(STD_ERR, "Error: %s\n", zErr);
 +  return 0;
 +}
 +
 +/* Transfer selected parameters between two parameter tables, for save/load.
 + * Argument bSaveNotLoad determines transfer direction and other actions.
 + * If it is true, the store DB will be created if not existent, and its
 + * table for keeping parameters will be created. Or failure is returned.
 + * If it is false, the store DB will be opened for read and its presumed
 + * table for keeping parameters will be read. Or failure is returned.
 + *
 + * Arguments azNames and nNames reference the ?NAMES? save/load arguments.
 + * If it is an empty list, all parameters will be saved or loaded.
 + * Otherwise, only the named parameters are transferred, if they exist.
 + * It is not an error to specify a name that cannot be transferred
 + * because it does not exist in the source table.
 + *
 + * Returns are SQLITE_OK for success, or other codes for failure.
 + */
 +static int kv_xfr_table(sqlite3 *db, const char *zStoreDbName,
 +                        int bSaveNotLoad, ParamTableUse ptu,
 +                        const char *azNames[], int nNames){
 +  int rc = 0;
 +  char *zSql = 0; /* to be sqlite3_free()'ed */
 +  sqlite3_str *sbCopy = 0;
 +  const char *zHere = 0;
 +  const char *zThere = SH_KV_STORE_SNAME;
 +  const char *zTo;
 +  const char *zFrom;
 +  sqlite3 *dbStore = 0;
 +  int openFlags = (bSaveNotLoad)
 +    ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE
 +    : SQLITE_OPEN_READONLY;
 +
 +  switch( ptu ){
 +  case PTU_Binding: zHere = PARAM_TABLE_SNAME; break;
 +  case PTU_Script:  zHere = SHVAR_TABLE_SNAME; break;
 +  default: assert(0); return 1;
 +  }
 +  zTo = (bSaveNotLoad)? zThere : zHere;
 +  zFrom = (bSaveNotLoad)? zHere : zThere;
 +  /* Ensure store DB can be opened and/or created appropriately. */
 +  rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0);
 +  if( rc!=SQLITE_OK ){
 +    utf8_printf(STD_ERR, "Error: Cannot %s key/value store DB %s\n",
 +                bSaveNotLoad? "open/create" : "read", zStoreDbName);
 +    return rc;
 +  }
 +  /* Ensure it has the kv store table, or handle its absence. */
 +  assert(dbStore!=0);
 +  if( sqlite3_table_column_metadata
 +      (dbStore, "main", SH_KV_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){
 +    if( !bSaveNotLoad ){
 +      utf8_printf(STD_ERR, "Error: No key/value pairs ever stored in DB %s\n",
 +                  zStoreDbName);
 +      rc = 1;
 +    }else{
 +      /* The saved parameters table is not there yet; create it. */
 +      const char *zCT =
 +        "CREATE TABLE IF NOT EXISTS "SH_KV_STORE_NAME"(\n"
 +        "  key TEXT PRIMARY KEY,\n"
 +        "  value,\n"
 +        "  uses INT\n"
 +        ") WITHOUT ROWID;";
 +      rc = sqlite3_exec(dbStore, zCT, 0, 0, 0);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "Cannot create table %s. Nothing saved.", zThere);
 +      }
      }
 -    while(  (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
 -    sqlite3_backup_finish(pBackup);
 -    if( rc==SQLITE_DONE ){
 -      rc = 0;
 +  }
 +  sqlite3_close(dbStore);
 +  if( rc!=0 ) return rc;
 +
 +  zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, SH_KV_STORE_SCHEMA);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
 +  if( rc!=SQLITE_OK ) return rc;
 +
 +  sbCopy = sqlite3_str_new(db);
 +  sqlite3_str_appendf
 +      (sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)"
 +       "SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom);
 +  append_in_clause(sbCopy, azNames, azNames+nNames);
 +  zSql = sqlite3_str_finish(sbCopy);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
 +
 +  sqlite3_exec(db, "DETACH "SH_KV_STORE_SCHEMA";", 0, 0, 0);
 +  return rc;
 +}
 +
 +/* Default locations of kv store DBs for .parameters and .vars save/load. */
 +static const char *zDefaultParamStore = "~/sqlite_params.sdb";
 +static const char *zDefaultVarStore = "~/sqlite_vars.sdb";
 +
 +/* Possibly generate a derived path from input spec, with defaulting
 + * and conversion of leading (or only) tilde as home directory.
 + * The above-set default is used for zSpec NULL, "" or "~".
 + * When return is 0, there is an error; what needs doing cannnot be done.
 + * If the return is exactly the input, it must not be sqlite3_free()'ed.
 + * If the return differs from the input, it must be sqlite3_free()'ed.
 + */
 +  static const char *kv_store_path(const char *zSpec, ParamTableUse ptu){
 +  if( zSpec==0 || zSpec[0]==0 || cli_strcmp(zSpec,"~")==0 ){
 +    const char *zDef;
 +    switch( ptu ){
 +    case PTU_Binding: zDef = zDefaultParamStore; break;
 +    case PTU_Script:  zDef = zDefaultVarStore; break;
 +    default: return 0;
 +    }
 +    return home_based_path(zDef);
 +  }else if ( zSpec[0]=='~' ){
 +    return home_based_path(zSpec);
 +  }
 +  return zSpec;
 +}
 +
 +/* Load some or all kv pairs. Arguments are "load FILE ?NAMES?". */
 +static int kv_pairs_load(sqlite3 *db, ParamTableUse ptu,
 +                         const char *azArg[], int nArg){
 +  const char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
 +  if( zStore==0 ){
 +    utf8_printf(STD_ERR, "Cannot form parameter load path. Nothing loaded.\n");
 +    return DCR_Error;
 +  }else{
 +    const char **pzFirst = (nArg>2)? azArg+2 : 0;
 +    int nNames = (nArg>2)? nArg-2 : 0;
 +    int rc = kv_xfr_table(db, zStore, 0, ptu, pzFirst, nNames);
 +    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
 +    return rc;
 +  }
 +}
 +
 +/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
 +static int kv_pairs_save(sqlite3 *db, ParamTableUse ptu,
 +                         const char *azArg[], int nArg){
 +  const char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
 +  if( zStore==0 ){
 +    utf8_printf(STD_ERR, "Cannot form parameter save path. Nothing saved.\n");
 +    return DCR_Error;
 +  }else{
 +    const char **pzFirst = (nArg>2)? azArg+2 : 0;
 +    int nNames = (nArg>2)? nArg-2 : 0;
 +    int rc = kv_xfr_table(db, zStore, 1, ptu, pzFirst, nNames);
 +    if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
 +    return rc;
 +  }
 +}
 +
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +/*
 + * Edit one named value in the parameters or shell variables table.
 + * If it does not yet exist, create it. If eval is true, the value
 + * is treated as a bare expression, otherwise it is a text value.
 + * The "uses" argument sets the 3rd column in the selected table,
 + * and serves to select which of the two tables is modified.
 + */
 +static int edit_one_kvalue(sqlite3 *db, char *name, int eval,
 +                           ParamTableUse uses, const char * zEditor){
 +  struct keyval_row kvRow = {0,0,0};
 +  int rc;
 +  char * zVal = 0;
 +  const char *zTab = 0;
 +  char * zSql = 0;
 +
 +  switch( uses ){
 +  case PTU_Script:  zTab = SHVAR_TABLE_SNAME; break;
 +  case PTU_Binding: zTab = PARAM_TABLE_SNAME; break;
 +  default: assert(0);
 +  }
 +  zSql = smprintf("SELECT value, uses FROM %s "
 +                  "WHERE key=%Q AND uses=%d", zTab, name, uses);
 +  shell_check_oom(zSql);
 +  sqlite3_exec(db, zSql, kv_find_callback, &kvRow, 0);
 +  sqlite3_free(zSql);
 +  assert(kvRow.hits<2);
 +  if( kvRow.hits==1 && kvRow.uses==uses){
 +    /* Editing an existing value of same kind. */
 +    sqlite3_free(kvRow.value);
 +    if( eval!=0 ){
 +      zSql = smprintf("SELECT edit(value, %Q) FROM %s "
 +                      "WHERE key=%Q AND uses=%d", zEditor, zTab, name, uses);
 +      shell_check_oom(zSql);
 +      zVal = db_text(db, zSql, 1);
 +      sqlite3_free(zSql);
 +      zSql = smprintf("UPDATE %s SET value=(SELECT %s) "
 +                      "WHERE key=%Q AND uses=%d", zTab, zVal, name, uses);
      }else{
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
 -      rc = 1;
 +      zSql = smprintf("UPDATE %s SET value=edit(value, %Q) WHERE"
 +                      " key=%Q AND uses=%d", zTab, zEditor, name, uses);
      }
 -    close_db(pDest);
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +  }else{
 +    /* Editing a new value of same kind. */
 +    assert(kvRow.value==0 || kvRow.uses!=uses);
 +    if( eval!=0 ){
 +      zSql = smprintf("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor);
 +      zVal = db_text(db, zSql, 1);
 +      sqlite3_free(zSql);
 +      zSql = smprintf("INSERT INTO %s(key,value,uses)"
 +         " VALUES (%Q,(SELECT %s LIMIT 1),%d)",
 +         zTab, name, zVal, uses);
 +    }else{
 +      zSql = smprintf("INSERT INTO %s(key,value,uses)"
 +                      " VALUES (%Q,edit('-- %q;%s', %Q),%d)",
 +                      zTab, name, name, "\n", zEditor, uses);
 +    }
 +  }
 +  shell_check_oom(zSql);
 +  rc = sqlite3_exec(db, zSql, 0, 0, 0);
 +  sqlite3_free(zSql);
 +  sqlite3_free(zVal);
 +  return rc!=SQLITE_OK;
 +}
 +#endif
 +
 +/* Space-join values in an argument list. *valLim is not included. */
 +char *values_join( char **valBeg, char **valLim ){
 +  char *z = 0;
 +  const char *zSep = 0;
 +  while( valBeg < valLim ){
 +    z = smprintf("%z%s%s", z, zSep, *valBeg);
 +    zSep = " ";
 +    ++valBeg;
 +  }
 +  return z;
 +}
 +
 +static struct ParamSetOpts {
 +  const char cCast;
 +  const char *zTypename;
 +  int evalKind;
 +} param_set_opts[] = {
 +  /* { 'q', 0, 2 }, */
 +  /* { 'x', 0, 1 }, */
 +  { 'i', "INT", 1 },
 +  { 'r', "REAL", 1 },
 +  { 'b', "BLOB", 1 },
 +  { 't', "TEXT", 0 },
 +  { 'n', "NUMERIC", 1 }
 +};
 +
 +/* Return an option character if it is single and prefixed by - or --,
 + * else return 0.
 + */
 +static char option_char(char *zArg){
 +  if( zArg[0]=='-' ){
 +    ++zArg;
 +    if( zArg[0]=='-' ) ++zArg;
 +    if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0];
 +  }
 +  return 0;
 +}
 +
 +/* Most of .vars set
 + * Return SQLITE_OK on success, else SQLITE_ERROR.
 + */
 +static int shvar_set(sqlite3 *db, char *name, char **valBeg, char **valLim){
 +  int rc = SQLITE_OK;
 +  char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
 +  sqlite3_stmt *pStmtSet = 0;
 +  char *zValue = (zValGlom==0)? *valBeg : zValGlom;
 +  char *zSql
 +    = smprintf("REPLACE INTO "SHVAR_TABLE_SNAME"(key,value,uses)"
 +               "VALUES(%Q,%Q,"SPTU_Script");", name, zValue);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 +  assert(rc==SQLITE_OK);
 +  sqlite3_free(zSql);
 +  rc = (SQLITE_DONE==sqlite3_step(pStmtSet))? SQLITE_OK : SQLITE_ERROR;
 +  sqlite3_finalize(pStmtSet);
 +  sqlite3_free(zValGlom);
 +  return rc;
 +}
  
 -  if( c=='b' && n>=3 && cli_strncmp(azArg[0], "bail", n)==0 ){
 -    if( nArg==2 ){
 -      bail_on_error = booleanValue(azArg[1]);
 +
 +/* Most of the .parameter set subcommand (per help text)
 + */
 +static int param_set(sqlite3 *db, char cCast,
 +                     char *name, char **valBeg, char **valLim){
 +  char *zSql = 0;
 +  int rc = SQLITE_OK, retries = 0, needsEval = 1;
 +  char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
 +  sqlite3_stmt *pStmtSet = 0;
 +  const char *zCastTo = 0;
 +  char *zValue = (zValGlom==0)? *valBeg : zValGlom;
 +  if( cCast ){
 +    struct ParamSetOpts *pSO = param_set_opts;
 +    for(; pSO-param_set_opts < ArraySize(param_set_opts); ++pSO ){
 +      if( cCast==pSO->cCast ){
 +        zCastTo = pSO->zTypename;
 +        needsEval = pSO->evalKind > 0;
 +        break;
 +      }
 +    }
 +  }
 +  if( needsEval ){
 +    if( zCastTo!=0 ){
 +      zSql = smprintf
 +        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +          " VALUES(%Q,CAST((%s) AS %s),"SPTU_Binding");",
 +          name, zValue, zCastTo );
      }else{
 -      raw_printf(stderr, "Usage: .bail on|off\n");
 -      rc = 1;
 +      zSql = smprintf
 +        ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +          "VALUES(%Q,(%s),"SPTU_Binding");", name, zValue );
      }
 -  }else
 +    shell_check_oom(zSql);
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 +    sqlite3_free(zSql);
 +  }
 +  if( !needsEval || rc!=SQLITE_OK ){
 +    /* Reach here when value either requested to be cast to text, or must be. */
 +    sqlite3_finalize(pStmtSet);
 +    pStmtSet = 0;
 +    zSql = smprintf
 +      ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
 +        "VALUES(%Q,%Q,"SPTU_Binding");", name, zValue );
 +    shell_check_oom(zSql);
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
 +    assert(rc==SQLITE_OK);
 +    sqlite3_free(zSql);
 +  }
 +  sqlite3_step(pStmtSet);
 +  sqlite3_finalize(pStmtSet);
 +  sqlite3_free(zValGlom);
 +  return rc;
 +}
  
 -  if( c=='b' && n>=3 && cli_strncmp(azArg[0], "binary", n)==0 ){
 -    if( nArg==2 ){
 -      if( booleanValue(azArg[1]) ){
 -        setBinaryMode(p->out, 1);
 -      }else{
 -        setTextMode(p->out, 1);
 +/* list or ls subcommand for .parameter and .vars dot-commands */
 +static void list_pov_entries(ShellExState *psx, ParamTableUse ptu, u8 bShort,
 +                             char **pzArgs, int nArg){
 +  sqlite3_stmt *pStmt = 0;
 +  sqlite3_str *sbList;
 +  int len = 0, rc;
 +  char *zFromWhere = 0;
 +  char *zSql = 0;
 +  sqlite3 *db;
 +  const char *zTab;
 +
 +  switch( ptu ){
 +  case PTU_Binding:
 +    db = DBX(psx);
 +    zTab = PARAM_TABLE_SNAME;
 +    break;
 +  case PTU_Script:
 +    db = psx->dbShell;
 +    zTab = SHVAR_TABLE_NAME;
 +    break;
 +  default: assert(0); return;
 +  }
 +  sbList = sqlite3_str_new(db);
 +  sqlite3_str_appendf(sbList, "FROM ");
 +  sqlite3_str_appendf(sbList, zTab);
 +  sqlite3_str_appendf(sbList, " WHERE (uses=?1) AND ");
 +  append_glob_terms(sbList, "key",
 +                    (const char **)pzArgs, (const char **)pzArgs+nArg);
 +  zFromWhere = sqlite3_str_finish(sbList);
 +  shell_check_oom(zFromWhere);
 +  zSql = smprintf("SELECT max(length(key)) %s", zFromWhere);
 +  shell_check_oom(zSql);
 +  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +  if( rc==SQLITE_OK ){
 +    sqlite3_bind_int(pStmt, 1, ptu);
 +    if( sqlite3_step(pStmt)==SQLITE_ROW ){
 +      len = sqlite3_column_int(pStmt, 0);
 +      if( len>40 ) len = 40;
 +      if( len<4 ) len = 4;
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  pStmt = 0;
 +  if( len ){
 +    FILE *out = ISS(psx)->out;
 +    sqlite3_free(zSql);
 +    if( !bShort ){
 +      int nBindings = 0, nScripts = 0;
 +      zSql = smprintf("SELECT key, uses,"
 +                      " iif(typeof(value)='text', quote(value), value) as v"
 +                      " %z ORDER BY uses, key", zFromWhere);
 +      shell_check_oom(zSql);
 +      rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +      sqlite3_bind_int(pStmt, 1, ptu);
 +      while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +        ParamTableUse ptux = sqlite3_column_int(pStmt,1);
 +        switch( ptux ){
 +        case PTU_Binding:
 +          if( nBindings++ == 0 ){
 +            utf8_printf(out, "Bindings:\n%-*s %s\n",
 +                        len, "name", "value");
 +          }
 +          utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
 +                      sqlite3_column_text(pStmt,2));
 +          break;
 +        case PTU_Script:
 +          if( nScripts++ == 0 ){
 +            utf8_printf(out, "Scripts:\n%-*s %s\n", len, "name", "value");
 +          }
 +          utf8_printf(out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
 +                      sqlite3_column_text(pStmt,2));
 +          break;
 +        default: break; /* Ignore */
 +        }
        }
      }else{
 -      raw_printf(stderr, "Usage: .binary on|off\n");
 -      rc = 1;
 +      int nc = 0, ncw = 78/(len+2);
 +      zSql = smprintf("SELECT key %z ORDER BY key", zFromWhere);
 +      shell_check_oom(zSql);
 +      rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +      sqlite3_bind_int(pStmt, 1, ptu);
 +      while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +        utf8_printf(out, "%s  %-*s", ((++nc%ncw==0)? "\n" : ""),
 +                    len, sqlite3_column_text(pStmt,0));
 +      }
 +      if( nc>0 ) utf8_printf(out, "\n");
      }
 -  }else
 +    sqlite3_finalize(pStmt);
 +  }else{
 +    sqlite3_free(zFromWhere);
 +  }
 +  sqlite3_free(zSql);
 +}
  
 -  /* The undocumented ".breakpoint" command causes a call to the no-op
 -  ** routine named test_breakpoint().
 -  */
 -  if( c=='b' && n>=3 && cli_strncmp(azArg[0], "breakpoint", n)==0 ){
 -    test_breakpoint();
 -  }else
 +/* Append an OR'ed series of GLOB terms comparing a given column
 + * name to a series of patterns. Result is an appended expression.
 + * For an empty pattern series, expression is true for non-NULL.
 + */
 +static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
 +                              const char **azBeg, const char **azLim){
 +  if( azBeg==azLim ) sqlite3_str_appendf(pStr, "%s NOT NULL", zColName);
 +  else{
 +    char *zSep = "(";
 +    while( azBeg<azLim ){
 +      sqlite3_str_appendf(pStr, "%sglob(%Q,%s)", zSep, *azBeg, zColName);
 +      zSep = " OR ";
 +      ++azBeg;
 +    }
 +    sqlite3_str_appendf(pStr, ")");
 +  }
 +}
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( c=='c' && cli_strcmp(azArg[0],"cd")==0 ){
 -    failIfSafeMode(p, "cannot run .cd in safe mode");
 -    if( nArg==2 ){
 -#if defined(_WIN32) || defined(WIN32)
 -      wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
 -      rc = !SetCurrentDirectoryW(z);
 -      sqlite3_free(z);
 -#else
 -      rc = chdir(azArg[1]);
 +/* Append either an IN clause or an always true test to some SQL.
 + *
 + * An empty IN list is the same as always true (for non-NULL LHS)
 + * for this clause, which assumes a trailing LHS operand and space.
 + * If that is not the right result, guard the call against it.
 + * This is used for ".parameter dostuff ?NAMES?" options,
 + * where a missing list means all the qualifying entries.
 + *
 + * The empty list may be signified by azBeg and azLim both 0.
 + */
 +static void append_in_clause(sqlite3_str *pStr,
 +                             const char **azBeg, const char **azLim){
 +  if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL");
 +  else{
 +    char cSep = '(';
 +    sqlite3_str_appendf(pStr, "IN");
 +    while( azBeg<azLim ){
 +      sqlite3_str_appendf(pStr, "%c%Q", cSep, *azBeg);
 +      cSep = ',';
 +      ++azBeg;
 +    }
 +    sqlite3_str_appendf(pStr, ")");
 +  }
 +}
 +
 +/*****************
 + * The .parameter command
 + */
 +COLLECT_HELP_TEXT[
 +  ".parameter CMD ...       Manage per-DB SQL parameter bindings table",
 +  "   clear ?NAMES?           Erase all or only given named parameters",
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  "   edit ?OPT? NAME ...     Use edit() to create or alter parameter NAME",
 +  "      OPT may be -t to use edited value as text or -e to evaluate it.",
  #endif
 -      if( rc ){
 -        utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]);
 -        rc = 1;
 +  "   init                    Initialize TEMP table for bindings and scripts",
 +  "   list ?PATTERNS?         List current DB parameters table binding values",
 +  "      Alternatively, to list just some or all names: ls ?PATTERNS?",
 +  "   load ?FILE? ?NAMES?     Load some or all named parameters from FILE",
 +  "      If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
 +  "   save ?FILE? ?NAMES?     Save some or all named parameters into FILE",
 +  "      If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
 +  "   set ?TOPT? NAME VALUE   Give SQL parameter NAME a value of VALUE",
 +  "      NAME must begin with one of $,:,@,? for bindings, VALUE is the space-",
 +  "      joined argument list. TOPT may be one of {-b -i -n -r -t} to cast the",
 +  "      effective value to BLOB, INT, NUMERIC, REAL or TEXT respectively.",
 +  "   unset ?NAMES?           Remove named parameter(s) from parameters table",
 +];
 +DISPATCHABLE_COMMAND( parameter 2 2 0 ){
 +  int rc = 0;
 +  open_db(p,0);
 +  sqlite3 *db = DBX(p);
 +
 +  /* .parameter clear  and  .parameter unset ?NAMES?
 +  **  Delete some or all parameters from the TEMP table that holds them.
 +  **  Without any arguments, clear deletes them all and unset does nothing.
 +  */
 +  if( cli_strcmp(azArg[1],"clear")==0 || cli_strcmp(azArg[1],"unset")==0 ){
 +    if( param_table_exists(db) && (nArg>2 || azArg[1][0]=='c') ){
 +      sqlite3_str *sbZap = sqlite3_str_new(db);
 +      char *zSql;
 +      sqlite3_str_appendf
 +        (sbZap, "DELETE FROM "PARAM_TABLE_SNAME" WHERE key ");
 +      append_in_clause(sbZap,
 +                       (const char **)&azArg[2], (const char **)&azArg[nArg]);
 +      zSql = sqlite3_str_finish(sbZap);
 +      shell_check_oom(zSql);
 +      sqlite3_exec(db, zSql, 0, 0, 0);
 +      sqlite3_free(zSql);
 +    }
 +  }else
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  /* .parameter edit ?NAMES?
 +  ** Edit the named parameters. Any that do not exist are created.
 +  */
 +  if( cli_strcmp(azArg[1],"edit")==0 ){
 +    ShellInState *psi = ISS(p);
 +    int ia = 2;
 +    int eval = 0;
 +    int edSet;
 +
 +    if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
 +      utf8_printf(STD_ERR, "Error: "
 +                  ".parameter edit can only be used interactively.\n");
 +      return DCR_Error;
 +    }
 +    param_table_init(db);
 +    edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 );
 +    if( edSet < 0 ) return DCR_Error;
 +    else ia += edSet;
 +    /* Future: Allow an option whereby new value can be evaluated
 +     * the way that .parameter set ... does.
 +     */
 +    while( ia < nArg ){
 +      ParamTableUse ptu;
 +      char *zA = azArg[ia];
 +      char cf = (zA[0]=='-')? zA[1] : 0;
 +      if( cf!=0 && zA[2]==0 ){
 +        ++ia;
 +        switch( cf ){
 +        case 'e': eval = 1; continue;
 +        case 't': eval = 0; continue;
 +        default:
 +          utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n", zA);
 +          return DCR_Error;
 +        }
        }
 -    }else{
 -      raw_printf(stderr, "Usage: .cd DIRECTORY\n");
 -      rc = 1;
 +      ptu = classify_param_name(zA);
 +      if( ptu!=PTU_Binding ){
 +        utf8_printf(STD_ERR, "Error: "
 +                    "%s cannot be a binding parameter name.\n", zA);
 +        return DCR_Error;
 +      }
 +      rc = edit_one_kvalue(db, zA, eval, ptu, psi->zEditor);
 +      ++ia;
 +      if( rc!=0 ) return DCR_Error;
      }
    }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +#endif
  
 -  if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){
 -    if( nArg==2 ){
 -      setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .changes on|off\n");
 -      rc = 1;
 -    }
 +  /* .parameter init
 +  ** Make sure the TEMP table used to hold bind parameters exists.
 +  ** Create it if necessary.
 +  */
 +  if( nArg==2 && cli_strcmp(azArg[1],"init")==0 ){
 +    param_table_init(db);
    }else
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  /* Cancel output redirection, if it is currently set (by .testcase)
 -  ** Then read the content of the testcase-out.txt file and compare against
 -  ** azArg[1].  If there are differences, report an error and exit.
 +  /* .parameter list|ls
 +  ** List all or selected bind parameters.
 +  ** list displays names, values and uses.
 +  ** ls displays just the names.
    */
 -  if( c=='c' && n>=3 && cli_strncmp(azArg[0], "check", n)==0 ){
 -    char *zRes = 0;
 -    output_reset(p);
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .check GLOB-PATTERN\n");
 -      rc = 2;
 -    }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
 -      rc = 2;
 -    }else if( testcase_glob(azArg[1],zRes)==0 ){
 -      utf8_printf(stderr,
 -                 "testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
 -                 p->zTestcase, azArg[1], zRes);
 -      rc = 1;
 -    }else{
 -      utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase);
 -      p->nCheck++;
 -    }
 -    sqlite3_free(zRes);
 +  if( nArg>=2 && ((cli_strcmp(azArg[1],"list")==0)
 +                  || (cli_strcmp(azArg[1],"ls")==0)) ){
 +    list_pov_entries(p, PTU_Binding, azArg[1][1]=='s', azArg+2, nArg-2);
    }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( c=='c' && cli_strncmp(azArg[0], "clone", n)==0 ){
 -    failIfSafeMode(p, "cannot run .clone in safe mode");
 -    if( nArg==2 ){
 -      tryToClone(p, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .clone FILENAME\n");
 -      rc = 1;
 -    }
 +  /* .parameter load
 +  ** Load all or named parameters from specified or default (DB) file.
 +  */
 +  if( cli_strcmp(azArg[1],"load")==0 ){
 +    param_table_init(db);
 +    rc = kv_pairs_load(db, PTU_Binding, (const char **)azArg+1, nArg-1);
    }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
  
 -  if( c=='c' && cli_strncmp(azArg[0], "connection", n)==0 ){
 -    if( nArg==1 ){
 -      /* List available connections */
 -      int i;
 -      for(i=0; i<ArraySize(p->aAuxDb); i++){
 -        const char *zFile = p->aAuxDb[i].zDbFilename;
 -        if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){
 -          zFile = "(not open)";
 -        }else if( zFile==0 ){
 -          zFile = "(memory)";
 -        }else if( zFile[0]==0 ){
 -          zFile = "(temporary-file)";
 -        }
 -        if( p->pAuxDb == &p->aAuxDb[i] ){
 -          utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile);
 -        }else if( p->aAuxDb[i].db!=0 ){
 -          utf8_printf(stdout, "       %d: %s\n", i, zFile);
 -        }
 -      }
 -    }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
 -      int i = azArg[1][0] - '0';
 -      if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && i<ArraySize(p->aAuxDb) ){
 -        p->pAuxDb->db = p->db;
 -        p->pAuxDb = &p->aAuxDb[i];
 -        globalDb = p->db = p->pAuxDb->db;
 -        p->pAuxDb->db = 0;
 -      }
 -    }else if( nArg==3 && cli_strcmp(azArg[1], "close")==0
 -           && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
 -      int i = azArg[2][0] - '0';
 -      if( i<0 || i>=ArraySize(p->aAuxDb) ){
 -        /* No-op */
 -      }else if( p->pAuxDb == &p->aAuxDb[i] ){
 -        raw_printf(stderr, "cannot close the active database connection\n");
 -        rc = 1;
 -      }else if( p->aAuxDb[i].db ){
 -        session_close_all(p, i);
 -        close_db(p->aAuxDb[i].db);
 -        p->aAuxDb[i].db = 0;
 -      }
 -    }else{
 -      raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n");
 -      rc = 1;
 -    }
 +  /* .parameter save
 +  ** Save all or named parameters into specified or default (DB) file.
 +  */
 +  if( cli_strcmp(azArg[1],"save")==0 ){
 +    rc = kv_pairs_save(db, PTU_Binding, (const char **)azArg+1, nArg-1);
    }else
  
 -  if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){
 -    char **azName = 0;
 -    int nName = 0;
 -    sqlite3_stmt *pStmt;
 -    int i;
 -    open_db(p, 0);
 -    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 -    if( rc ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 +  /* .parameter set NAME VALUE
 +  ** Set or reset a bind parameter.  NAME should be the full parameter
 +  ** name exactly as it appears in the query.  (ex: $abc, @def).  The
 +  ** VALUE can be in either SQL literal notation, or if not it will be
 +  ** understood to be a text string.
 +  */
 +  if( nArg>=4 && cli_strcmp(azArg[1],"set")==0 ){
 +    char cCast = option_char(azArg[2]);
 +    int inv = 2 + (cCast != 0);
 +    ParamTableUse ptu = classify_param_name(azArg[inv]);
 +    if( ptu!=PTU_Binding ){
 +      utf8_printf(STD_ERR,
 +                  "Error: %s is not a usable parameter name.\n", azArg[inv]);
        rc = 1;
      }else{
 -      while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -        const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
 -        const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
 -        if( zSchema==0 || zFile==0 ) continue;
 -        azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
 -        shell_check_oom(azName);
 -        azName[nName*2] = strdup(zSchema);
 -        azName[nName*2+1] = strdup(zFile);
 -        nName++;
 -      }
 -    }
 -    sqlite3_finalize(pStmt);
 -    for(i=0; i<nName; i++){
 -      int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
 -      int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
 -      const char *z = azName[i*2+1];
 -      utf8_printf(p->out, "%s: %s %s%s\n",
 -         azName[i*2],
 -         z && z[0] ? z : "\"\"",
 -         bRdonly ? "r/o" : "r/w",
 -         eTxn==SQLITE_TXN_NONE ? "" :
 -            eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
 -      free(azName[i*2]);
 -      free(azName[i*2+1]);
 -    }
 -    sqlite3_free(azName);
 -  }else
 -
 -  if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbconfig", n)==0 ){
 -    static const struct DbConfigChoices {
 -      const char *zName;
 -      int op;
 -    } aDbConfig[] = {
 -        { "defensive",          SQLITE_DBCONFIG_DEFENSIVE             },
 -        { "dqs_ddl",            SQLITE_DBCONFIG_DQS_DDL               },
 -        { "dqs_dml",            SQLITE_DBCONFIG_DQS_DML               },
 -        { "enable_fkey",        SQLITE_DBCONFIG_ENABLE_FKEY           },
 -        { "enable_qpsg",        SQLITE_DBCONFIG_ENABLE_QPSG           },
 -        { "enable_trigger",     SQLITE_DBCONFIG_ENABLE_TRIGGER        },
 -        { "enable_view",        SQLITE_DBCONFIG_ENABLE_VIEW           },
 -        { "fts3_tokenizer",     SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
 -        { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE    },
 -        { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT    },
 -        { "load_extension",     SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
 -        { "no_ckpt_on_close",   SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE      },
 -        { "reset_database",     SQLITE_DBCONFIG_RESET_DATABASE        },
 -        { "reverse_scanorder",  SQLITE_DBCONFIG_REVERSE_SCANORDER     },
 -        { "stmt_scanstatus",    SQLITE_DBCONFIG_STMT_SCANSTATUS       },
 -        { "trigger_eqp",        SQLITE_DBCONFIG_TRIGGER_EQP           },
 -        { "trusted_schema",     SQLITE_DBCONFIG_TRUSTED_SCHEMA        },
 -        { "writable_schema",    SQLITE_DBCONFIG_WRITABLE_SCHEMA       },
 -    };
 -    int ii, v;
 -    open_db(p, 0);
 -    for(ii=0; ii<ArraySize(aDbConfig); ii++){
 -      if( nArg>1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
 -      if( nArg>=3 ){
 -        sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
 +      param_table_init(db);
 +      rc = param_set(db, cCast, azArg[inv], &azArg[inv+1], &azArg[nArg]);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(db));
 +        rc = 1;
        }
 -      sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
 -      utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
 -      if( nArg>1 ) break;
 -    }
 -    if( nArg>1 && ii==ArraySize(aDbConfig) ){
 -      utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
 -      utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
      }
    }else
  
 -#if SQLITE_SHELL_HAVE_RECOVER
 -  if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbinfo", n)==0 ){
 -    rc = shell_dbinfo_command(p, nArg, azArg);
 -  }else
 -
 -  if( c=='r' && cli_strncmp(azArg[0], "recover", n)==0 ){
 -    open_db(p, 0);
 -    rc = recoverDatabaseCmd(p, nArg, azArg);
 -  }else
 -#endif /* SQLITE_SHELL_HAVE_RECOVER */
 +  {  /* If no command name and arg count matches, show a syntax error */
 +    showHelp(ISS(p)->out, "parameter", p);
 +    return DCR_CmdErred;
 +  }
  
 -  if( c=='d' && cli_strncmp(azArg[0], "dump", n)==0 ){
 -    char *zLike = 0;
 -    char *zSql;
 -    int i;
 -    int savedShowHeader = p->showHeader;
 -    int savedShellFlags = p->shellFlgs;
 -    ShellClearFlag(p,
 -       SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
 -       |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
 -    for(i=1; i<nArg; i++){
 -      if( azArg[i][0]=='-' ){
 -        const char *z = azArg[i]+1;
 -        if( z[0]=='-' ) z++;
 -        if( cli_strcmp(z,"preserve-rowids")==0 ){
 -#ifdef SQLITE_OMIT_VIRTUALTABLE
 -          raw_printf(stderr, "The --preserve-rowids option is not compatible"
 -                             " with SQLITE_OMIT_VIRTUALTABLE\n");
 -          rc = 1;
 -          sqlite3_free(zLike);
 -          goto meta_command_exit;
 -#else
 -          ShellSetFlag(p, SHFLG_PreserveRowid);
 -#endif
 -        }else
 -        if( cli_strcmp(z,"newlines")==0 ){
 -          ShellSetFlag(p, SHFLG_Newlines);
 -        }else
 -        if( cli_strcmp(z,"data-only")==0 ){
 -          ShellSetFlag(p, SHFLG_DumpDataOnly);
 -        }else
 -        if( cli_strcmp(z,"nosys")==0 ){
 -          ShellSetFlag(p, SHFLG_DumpNoSys);
 -        }else
 -        {
 -          raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]);
 -          rc = 1;
 -          sqlite3_free(zLike);
 -          goto meta_command_exit;
 -        }
 -      }else{
 -        /* azArg[i] contains a LIKE pattern. This ".dump" request should
 -        ** only dump data for tables for which either the table name matches
 -        ** the LIKE pattern, or the table appears to be a shadow table of
 -        ** a virtual table for which the name matches the LIKE pattern.
 -        */
 -        char *zExpr = sqlite3_mprintf(
 -            "name LIKE %Q ESCAPE '\\' OR EXISTS ("
 -            "  SELECT 1 FROM sqlite_schema WHERE "
 -            "    name LIKE %Q ESCAPE '\\' AND"
 -            "    sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
 -            "    substr(o.name, 1, length(name)+1) == (name||'_')"
 -            ")", azArg[i], azArg[i]
 -        );
 +  return DCR_Ok | (rc!=0);
 +}
  
 -        if( zLike ){
 -          zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
 +/*****************
 + * The .print, .progress and .prompt commands
 + */
 +CONDITION_COMMAND( progress !defined(SQLITE_OMIT_PROGRESS_CALLBACK) );
 +COLLECT_HELP_TEXT[
 +  ".print STRING...         Print literal STRING",
 +  ".progress N              Invoke progress handler after every N opcodes",
 +  "   --limit N                 Interrupt after N progress callbacks",
 +  "   --once                    Do no more than one progress interrupt",
 +  "   --quiet|-q                No output except at interrupts",
 +  "   --reset                   Reset the count for each input and interrupt",
 +  ".prompt MAIN CONTINUE    Replace the standard prompts",
 +];
 +DISPATCHABLE_COMMAND( print 3 1 0 ){
 +  int i;
 +  for(i=1; i<nArg; i++){
 +    utf8_printf(ISS(p)->out, "%s%s", azArg[i], (i==nArg-1)? "\n" : " ");
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( progress 3 2 0 ){
 +  ShellInState *psi = ISS(p);
 +  int i;
 +  int nn = 0;
 +  psi->flgProgress = 0;
 +  psi->mxProgress = 0;
 +  psi->nProgress = 0;
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      z++;
 +      if( z[0]=='-' ) z++;
 +      if( cli_strcmp(z,"quiet")==0 || cli_strcmp(z,"q")==0 ){
 +        psi->flgProgress |= SHELL_PROGRESS_QUIET;
 +        continue;
 +      }
 +      if( cli_strcmp(z,"reset")==0 ){
 +        psi->flgProgress |= SHELL_PROGRESS_RESET;
 +        continue;
 +      }
 +      if( cli_strcmp(z,"once")==0 ){
 +        psi->flgProgress |= SHELL_PROGRESS_ONCE;
 +        continue;
 +      }
 +      if( cli_strcmp(z,"limit")==0 ){
 +        if( i+1>=nArg ){
 +          *pzErr = smprintf("missing argument on --limit\n");
 +          return DCR_Unpaired|i;
          }else{
 -          zLike = zExpr;
 +          psi->mxProgress = (int)integerValue(azArg[++i]);
          }
 +        continue;
        }
 +      return DCR_Unknown|i;
 +    }else{
 +      nn = (int)integerValue(z);
      }
 +  }
 +  open_db(p, 0);
 +  sqlite3_progress_handler(DBX(p), nn, progress_handler, psi);
 +  return DCR_Ok;
 +}
  
 -    open_db(p, 0);
 -
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      /* When playing back a "dump", the content might appear in an order
 -      ** which causes immediate foreign key constraints to be violated.
 -      ** So disable foreign-key constraint enforcement to prevent problems. */
 -      raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
 -      raw_printf(p->out, "BEGIN TRANSACTION;\n");
 -    }
 -    p->writableSchema = 0;
 -    p->showHeader = 0;
 -    /* Set writable_schema=ON since doing so forces SQLite to initialize
 -    ** as much of the schema as it can even if the sqlite_schema table is
 -    ** corrupt. */
 -    sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
 -    p->nErr = 0;
 -    if( zLike==0 ) zLike = sqlite3_mprintf("true");
 -    zSql = sqlite3_mprintf(
 -      "SELECT name, type, sql FROM sqlite_schema AS o "
 -      "WHERE (%s) AND type=='table'"
 -      "  AND sql NOT NULL"
 -      " ORDER BY tbl_name='sqlite_sequence', rowid",
 -      zLike
 -    );
 -    run_schema_dump_query(p,zSql);
 -    sqlite3_free(zSql);
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      zSql = sqlite3_mprintf(
 -        "SELECT sql FROM sqlite_schema AS o "
 -        "WHERE (%s) AND sql NOT NULL"
 -        "  AND type IN ('index','trigger','view')",
 -        zLike
 -      );
 -      run_table_dump_query(p, zSql);
 -      sqlite3_free(zSql);
 -    }
 -    sqlite3_free(zLike);
 -    if( p->writableSchema ){
 -      raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
 -      p->writableSchema = 0;
 -    }
 -    sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
 -    sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
 -    if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
 -      raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
 -    }
 -    p->showHeader = savedShowHeader;
 -    p->shellFlgs = savedShellFlags;
 -  }else
 +/* Allow too few arguments by tradition, (a form of no-op.) */
 +DISPATCHABLE_COMMAND( prompt ? 1 3 ){
 +  if( nArg >= 2) {
 +    SET_MAIN_PROMPT(azArg[1]);
 +  }
 +  if( nArg >= 3) {
 +    SET_MORE_PROMPT(azArg[2]);
 +  }
 +  return DCR_Ok;
 +}
  
 -  if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){
 -    if( nArg==2 ){
 -      setOrClearFlag(p, SHFLG_Echo, azArg[1]);
 -    }else{
 -      raw_printf(stderr, "Usage: .echo on|off\n");
 -      rc = 1;
 -    }
 -  }else
 +/*****************
 + * The .recover and .restore commands
 + */
 +CONDITION_COMMAND( recover SQLITE_SHELL_HAVE_RECOVER );
 +CONDITION_COMMAND( restore !defined(SQLITE_SHELL_FIDDLE) );
 +COLLECT_HELP_TEXT[
 +  ".recover                 Recover as much data as possible from corrupt db.",
 +  "   --ignore-freelist        Ignore pages that appear to be on db freelist",
 +  "   --lost-and-found TABLE   Alternative name for the lost-and-found table",
 +  "   --no-rowids              Do not attempt to recover rowid values",
 +  "                            that are not also INTEGER PRIMARY KEYs",
 +  ".restore ?DB? FILE       Restore content of DB (default \"main\") from FILE",
 +];
  
 -  if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){
 -    if( nArg==2 ){
 -      p->autoEQPtest = 0;
 -      if( p->autoEQPtrace ){
 -        if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
 -        p->autoEQPtrace = 0;
 -      }
 -      if( cli_strcmp(azArg[1],"full")==0 ){
 -        p->autoEQP = AUTOEQP_full;
 -      }else if( cli_strcmp(azArg[1],"trigger")==0 ){
 -        p->autoEQP = AUTOEQP_trigger;
 -#ifdef SQLITE_DEBUG
 -      }else if( cli_strcmp(azArg[1],"test")==0 ){
 -        p->autoEQP = AUTOEQP_on;
 -        p->autoEQPtest = 1;
 -      }else if( cli_strcmp(azArg[1],"trace")==0 ){
 -        p->autoEQP = AUTOEQP_full;
 -        p->autoEQPtrace = 1;
 -        open_db(p, 0);
 -        sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
 -        sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0);
 -#endif
 -      }else{
 -        p->autoEQP = (u8)booleanValue(azArg[1]);
 -      }
 -    }else{
 -      raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n");
 -      rc = 1;
 -    }
 -  }else
 +/*
 +** This command is invoked to recover data from the database. A script
 +** to construct a new database containing all recovered data is output
 +** on stream pState->out.
 +*/
 +DISPATCHABLE_COMMAND( recover ? 1 7 ){
 +  FILE *out = ISS(p)->out;
 +  sqlite3 *db;
 +  int rc = SQLITE_OK;
 +  sqlite3_stmt *pLoop = 0;        /* Loop through all root pages */
 +  sqlite3_stmt *pPages = 0;       /* Loop through all pages in a group */
 +  sqlite3_stmt *pCells = 0;       /* Loop through all cells in a page */
 +  const char *zRecoveryDb = "";   /* Name of "recovery" database */
 +  const char *zLostAndFound = "lost_and_found";
 +  int i;
 +  int nOrphan = -1;
 +  RecoverTable *pOrphan = 0;
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( c=='e' && cli_strncmp(azArg[0], "exit", n)==0 ){
 -    if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
 -    rc = 2;
 -  }else
 -#endif
 +  open_db(p, 0);
 +  db = DBX(p);
  
 -  /* The ".explain" command is automatic now.  It is largely pointless.  It
 -  ** retained purely for backwards compatibility */
 -  if( c=='e' && cli_strncmp(azArg[0], "explain", n)==0 ){
 -    int val = 1;
 -    if( nArg>=2 ){
 -      if( cli_strcmp(azArg[1],"auto")==0 ){
 -        val = 99;
 -      }else{
 -        val =  booleanValue(azArg[1]);
 -      }
 -    }
 -    if( val==1 && p->mode!=MODE_Explain ){
 -      p->normalMode = p->mode;
 -      p->mode = MODE_Explain;
 -      p->autoExplain = 0;
 -    }else if( val==0 ){
 -      if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 -      p->autoExplain = 0;
 -    }else if( val==99 ){
 -      if( p->mode==MODE_Explain ) p->mode = p->normalMode;
 -      p->autoExplain = 1;
 +  int bFreelist = 1;              /* 0 if --freelist-corrupt is specified */
 +  int bRowids = 1;                /* 0 if --no-rowids */
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    int n;
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    n = strlen30(z);
 +    if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){
 +      bFreelist = 0;
 +    }else
 +    if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
 +      i++;
 +      zRecoveryDb = azArg[i];
 +    }else
 +    if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
 +      i++;
 +      zLostAndFound = azArg[i];
 +    }else
 +    if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
 +      bRowids = 0;
      }
 -  }else
 +    else{
 +      *pzErr = smprintf("unexpected option: %s\n", azArg[i]);
 +      showHelp(out, azArg[0], p);
 +      return DCR_CmdErred;
 +    }
 +  }
 +
 +  shellExecPrintf(db, &rc,
 +    /* Attach an in-memory database named 'recovery'. Create an indexed
 +    ** cache of the sqlite_dbptr virtual table. */
 +    "PRAGMA writable_schema = on;"
 +    "ATTACH %Q AS recovery;"
 +    "DROP TABLE IF EXISTS recovery.dbptr;"
 +    "DROP TABLE IF EXISTS recovery.freelist;"
 +    "DROP TABLE IF EXISTS recovery.map;"
 +    "DROP TABLE IF EXISTS recovery.schema;"
 +    "CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb
 +  );
  
 -#ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){
 -    if( p->bSafeMode ){
 -      raw_printf(stderr,
 -        "Cannot run experimental commands such as \"%s\" in safe mode\n",
 -        azArg[0]);
 -      rc = 1;
 -    }else{
 -      open_db(p, 0);
 -      expertDotCommand(p, azArg, nArg);
 -    }
 -  }else
 -#endif
 +  if( bFreelist ){
 +    shellExec(db, &rc,
 +      "WITH trunk(pgno) AS ("
 +      "  SELECT shell_int32("
 +      "      (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x "
 +      "      WHERE x>0"
 +      "    UNION"
 +      "  SELECT shell_int32("
 +      "      (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x "
 +      "      FROM trunk WHERE x>0"
 +      "),"
 +      "freelist(data, n, freepgno) AS ("
 +      "  SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno "
 +      "      FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno"
 +      "    UNION ALL"
 +      "  SELECT data, n-1, shell_int32(data, 2+n) "
 +      "      FROM freelist WHERE n>=0"
 +      ")"
 +      "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
 +    );
 +  }
 +
 +  /* If this is an auto-vacuum database, add all pointer-map pages to
 +  ** the freelist table. Do this regardless of whether or not
 +  ** --freelist-corrupt was specified.  */
 +  shellExec(db, &rc,
 +    "WITH ptrmap(pgno) AS ("
 +    "  SELECT 2 WHERE shell_int32("
 +    "    (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
 +    "  )"
 +    "    UNION ALL "
 +    "  SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp "
 +    "  FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)"
 +    ")"
 +    "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
 +  );
  
 -  if( c=='f' && cli_strncmp(azArg[0], "filectrl", n)==0 ){
 -    static const struct {
 -       const char *zCtrlName;   /* Name of a test-control option */
 -       int ctrlCode;            /* Integer code for that option */
 -       const char *zUsage;      /* Usage notes */
 -    } aCtrl[] = {
 -      { "chunk_size",     SQLITE_FCNTL_CHUNK_SIZE,      "SIZE"           },
 -      { "data_version",   SQLITE_FCNTL_DATA_VERSION,    ""               },
 -      { "has_moved",      SQLITE_FCNTL_HAS_MOVED,       ""               },
 -      { "lock_timeout",   SQLITE_FCNTL_LOCK_TIMEOUT,    "MILLISEC"       },
 -      { "persist_wal",    SQLITE_FCNTL_PERSIST_WAL,     "[BOOLEAN]"      },
 -   /* { "pragma",         SQLITE_FCNTL_PRAGMA,          "NAME ARG"       },*/
 -      { "psow",       SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]"      },
 -      { "reserve_bytes",  SQLITE_FCNTL_RESERVE_BYTES,   "[N]"            },
 -      { "size_limit",     SQLITE_FCNTL_SIZE_LIMIT,      "[LIMIT]"        },
 -      { "tempfilename",   SQLITE_FCNTL_TEMPFILENAME,    ""               },
 -   /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY,  "COUNT DELAY"    },*/
 -    };
 -    int filectrl = -1;
 -    int iCtrl = -1;
 -    sqlite3_int64 iRes = 0;  /* Integer result to display if rc2==1 */
 -    int isOk = 0;            /* 0: usage  1: %lld  2: no-result */
 -    int n2, i;
 -    const char *zCmd = 0;
 -    const char *zSchema = 0;
 +  shellExec(db, &rc,
 +    "CREATE TABLE recovery.dbptr("
 +    "      pgno, child, PRIMARY KEY(child, pgno)"
 +    ") WITHOUT ROWID;"
 +    "INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
 +    "    SELECT * FROM sqlite_dbptr"
 +    "      WHERE pgno NOT IN freelist AND child NOT IN freelist;"
 +
 +    /* Delete any pointer to page 1. This ensures that page 1 is considered
 +    ** a root page, regardless of how corrupt the db is. */
 +    "DELETE FROM recovery.dbptr WHERE child = 1;"
 +
 +    /* Delete all pointers to any pages that have more than one pointer
 +    ** to them. Such pages will be treated as root pages when recovering
 +    ** data.  */
 +    "DELETE FROM recovery.dbptr WHERE child IN ("
 +    "  SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1"
 +    ");"
  
 -    open_db(p, 0);
 -    zCmd = nArg>=2 ? azArg[1] : "help";
 +    /* Create the "map" table that will (eventually) contain instructions
 +    ** for dealing with each page in the db that contains one or more
 +    ** records. */
 +    "CREATE TABLE recovery.map("
 +      "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT"
 +    ");"
  
 -    if( zCmd[0]=='-'
 -     && (cli_strcmp(zCmd,"--schema")==0 || cli_strcmp(zCmd,"-schema")==0)
 -     && nArg>=4
 -    ){
 -      zSchema = azArg[2];
 -      for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
 -      nArg -= 2;
 -      zCmd = azArg[1];
 -    }
 +    /* Populate table [map]. If there are circular loops of pages in the
 +    ** database, the following adds all pages in such a loop to the map
 +    ** as individual root pages. This could be handled better.  */
 +    "WITH pages(i, maxlen) AS ("
 +    "  SELECT page_count, ("
 +    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count"
 +    "  ) FROM pragma_page_count WHERE page_count>0"
 +    "    UNION ALL"
 +    "  SELECT i-1, ("
 +    "    SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1"
 +    "  ) FROM pages WHERE i>=2"
 +    ")"
 +    "INSERT INTO recovery.map(pgno, maxlen, intkey, root) "
 +    "  SELECT i, maxlen, NULL, ("
 +    "    WITH p(orig, pgno, parent) AS ("
 +    "      SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
 +    "        UNION "
 +    "      SELECT i, p.parent, "
 +    "        (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p"
 +    "    )"
 +    "    SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)"
 +    ") "
 +    "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;"
 +    "UPDATE recovery.map AS o SET intkey = ("
 +    "  SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno"
 +    ");"
  
 -    /* The argument can optionally begin with "-" or "--" */
 -    if( zCmd[0]=='-' && zCmd[1] ){
 -      zCmd++;
 -      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 -    }
 +    /* Extract data from page 1 and any linked pages into table
 +    ** recovery.schema. With the same schema as an sqlite_schema table.  */
 +    "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
 +    "INSERT INTO recovery.schema SELECT "
 +    "  max(CASE WHEN field=0 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=1 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=2 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=3 THEN value ELSE NULL END),"
 +    "  max(CASE WHEN field=4 THEN value ELSE NULL END)"
 +    "FROM sqlite_dbdata WHERE pgno IN ("
 +    "  SELECT pgno FROM recovery.map WHERE root=1"
 +    ")"
 +    "GROUP BY pgno, cell;"
 +    "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
 +  );
  
 -    /* --help lists all file-controls */
 -    if( cli_strcmp(zCmd,"help")==0 ){
 -      utf8_printf(p->out, "Available file-controls:\n");
 -      for(i=0; i<ArraySize(aCtrl); i++){
 -        utf8_printf(p->out, "  .filectrl %s %s\n",
 -                    aCtrl[i].zCtrlName, aCtrl[i].zUsage);
 -      }
 -      rc = 1;
 -      goto meta_command_exit;
 +  /* Open a transaction, then print out all non-virtual, non-"sqlite_%"
 +  ** CREATE TABLE statements that extracted from the existing schema.  */
 +  if( rc==SQLITE_OK ){
 +    sqlite3_stmt *pStmt = 0;
 +    /* ".recover" might output content in an order which causes immediate
 +    ** foreign key constraints to be violated. So disable foreign-key
 +    ** constraint enforcement to prevent problems when running the output
 +    ** script. */
 +    raw_printf(out, "PRAGMA foreign_keys=OFF;\n");
 +    raw_printf(out, "BEGIN;\n");
 +    raw_printf(out, "PRAGMA writable_schema = on;\n");
 +    shellPrepare(db, &rc,
 +        "SELECT sql FROM recovery.schema "
 +        "WHERE type='table' AND sql LIKE 'create table%'", &pStmt
 +    );
 +    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 +      const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0);
 +      raw_printf(out, "CREATE TABLE IF NOT EXISTS %s;\n", &zCreateTable[12]);
      }
 +    shellFinalize(&rc, pStmt);
 +  }
  
 -    /* convert filectrl text option to value. allow any unique prefix
 -    ** of the option name, or a numerical value. */
 -    n2 = strlen30(zCmd);
 -    for(i=0; i<ArraySize(aCtrl); i++){
 -      if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 -        if( filectrl<0 ){
 -          filectrl = aCtrl[i].ctrlCode;
 -          iCtrl = i;
 -        }else{
 -          utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n"
 -                              "Use \".filectrl --help\" for help\n", zCmd);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -      }
 -    }
 -    if( filectrl<0 ){
 -      utf8_printf(stderr,"Error: unknown file-control: %s\n"
 -                         "Use \".filectrl --help\" for help\n", zCmd);
 +  /* Figure out if an orphan table will be required. And if so, how many
 +  ** user columns it should contain */
 +  shellPrepare(db, &rc,
 +      "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
 +      , &pLoop
 +  );
 +  if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 +    nOrphan = sqlite3_column_int(pLoop, 0);
 +  }
 +  shellFinalize(&rc, pLoop);
 +  pLoop = 0;
 +
 +  shellPrepare(db, &rc,
 +      "SELECT pgno FROM recovery.map WHERE root=?", &pPages
 +  );
 +
 +  shellPrepare(db, &rc,
 +      "SELECT max(field), group_concat(shell_escape_crnl(quote"
 +      "(case when (? AND field<0) then NULL else value end)"
 +      "), ', ')"
 +      ", min(field) "
 +      "FROM sqlite_dbdata WHERE pgno = ? AND field != ?"
 +      "GROUP BY cell", &pCells
 +  );
 +
 +  /* Loop through each root page. */
 +  shellPrepare(db, &rc,
 +      "SELECT root, intkey, max(maxlen) FROM recovery.map"
 +      " WHERE root>1 GROUP BY root, intkey ORDER BY root=("
 +      "  SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
 +      ")", &pLoop
 +  );
 +  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
 +    int iRoot = sqlite3_column_int(pLoop, 0);
 +    int bIntkey = sqlite3_column_int(pLoop, 1);
 +    int nCol = sqlite3_column_int(pLoop, 2);
 +    int bNoop = 0;
 +    RecoverTable *pTab;
 +
 +    assert( bIntkey==0 || bIntkey==1 );
 +    pTab = recoverFindTable(db, &rc, iRoot, bIntkey, nCol, &bNoop);
 +    if( bNoop || rc ) continue;
 +    if( pTab==0 ){
 +      if( pOrphan==0 ){
 +        pOrphan = recoverOrphanTable(db, out, &rc, zLostAndFound, nOrphan);
 +      }
 +      pTab = pOrphan;
 +      if( pTab==0 ) break;
 +    }
 +
 +    if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
 +      raw_printf(out, "DELETE FROM sqlite_sequence;\n");
 +    }
 +    sqlite3_bind_int(pPages, 1, iRoot);
 +    if( bRowids==0 && pTab->iPk<0 ){
 +      sqlite3_bind_int(pCells, 1, 1);
      }else{
 -      switch(filectrl){
 -        case SQLITE_FCNTL_SIZE_LIMIT: {
 -          if( nArg!=2 && nArg!=3 ) break;
 -          iRes = nArg==3 ? integerValue(azArg[2]) : -1;
 -          sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_LOCK_TIMEOUT:
 -        case SQLITE_FCNTL_CHUNK_SIZE: {
 -          int x;
 -          if( nArg!=3 ) break;
 -          x = (int)integerValue(azArg[2]);
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          isOk = 2;
 -          break;
 -        }
 -        case SQLITE_FCNTL_PERSIST_WAL:
 -        case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
 -          int x;
 -          if( nArg!=2 && nArg!=3 ) break;
 -          x = nArg==3 ? booleanValue(azArg[2]) : -1;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          iRes = x;
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_DATA_VERSION:
 -        case SQLITE_FCNTL_HAS_MOVED: {
 -          int x;
 -          if( nArg!=2 ) break;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          iRes = x;
 -          isOk = 1;
 -          break;
 -        }
 -        case SQLITE_FCNTL_TEMPFILENAME: {
 -          char *z = 0;
 -          if( nArg!=2 ) break;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &z);
 -          if( z ){
 -            utf8_printf(p->out, "%s\n", z);
 -            sqlite3_free(z);
 +      sqlite3_bind_int(pCells, 1, 0);
 +    }
 +    sqlite3_bind_int(pCells, 3, pTab->iPk);
 +
 +    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
 +      int iPgno = sqlite3_column_int(pPages, 0);
 +      sqlite3_bind_int(pCells, 2, iPgno);
 +      while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
 +        int nField = sqlite3_column_int(pCells, 0);
 +        int iMin = sqlite3_column_int(pCells, 2);
 +        const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
 +
 +        RecoverTable *pTab2 = pTab;
 +        if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
 +          if( pOrphan==0 ){
 +            pOrphan = recoverOrphanTable(db, out, &rc, zLostAndFound, nOrphan);
            }
 -          isOk = 2;
 -          break;
 +          pTab2 = pOrphan;
 +          if( pTab2==0 ) break;
          }
 -        case SQLITE_FCNTL_RESERVE_BYTES: {
 -          int x;
 -          if( nArg>=3 ){
 -            x = atoi(azArg[2]);
 -            sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          }
 -          x = -1;
 -          sqlite3_file_control(p->db, zSchema, filectrl, &x);
 -          utf8_printf(p->out,"%d\n", x);
 -          isOk = 2;
 -          break;
 +
 +        nField = nField+1;
 +        if( pTab2==pOrphan ){
 +          raw_printf(out,
 +              "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
 +              pTab2->zQuoted, iRoot, iPgno, nField,
 +              iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
 +          );
 +        }else{
 +          raw_printf(out, "INSERT INTO %s(%s) VALUES( %s );\n",
 +              pTab2->zQuoted, pTab2->azlCol[nField], zVal
 +          );
          }
        }
 +      shellReset(&rc, pCells);
      }
 -    if( isOk==0 && iCtrl>=0 ){
 -      utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 -      rc = 1;
 -    }else if( isOk==1 ){
 -      char zBuf[100];
 -      sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
 -      raw_printf(p->out, "%s\n", zBuf);
 -    }
 -  }else
 +    shellReset(&rc, pPages);
 +    if( pTab!=pOrphan ) recoverFreeTable(pTab);
 +  }
 +  shellFinalize(&rc, pLoop);
 +  shellFinalize(&rc, pPages);
 +  shellFinalize(&rc, pCells);
 +  recoverFreeTable(pOrphan);
  
 -  if( c=='f' && cli_strncmp(azArg[0], "fullschema", n)==0 ){
 -    ShellState data;
 -    int doStats = 0;
 -    memcpy(&data, p, sizeof(data));
 -    data.showHeader = 0;
 -    data.cMode = data.mode = MODE_Semi;
 -    if( nArg==2 && optionMatch(azArg[1], "indent") ){
 -      data.cMode = data.mode = MODE_Pretty;
 -      nArg = 1;
 -    }
 -    if( nArg!=1 ){
 -      raw_printf(stderr, "Usage: .fullschema ?--indent?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    rc = sqlite3_exec(p->db,
 -       "SELECT sql FROM"
 -       "  (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
 -       "     FROM sqlite_schema UNION ALL"
 -       "   SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
 -       "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
 -       "ORDER BY x",
 -       callback, &data, 0
 +  /* The rest of the schema */
 +  if( rc==SQLITE_OK ){
 +    sqlite3_stmt *pStmt = 0;
 +    shellPrepare(db, &rc,
 +        "SELECT sql, name FROM recovery.schema "
 +        "WHERE sql NOT LIKE 'create table%'", &pStmt
      );
 -    if( rc==SQLITE_OK ){
 -      sqlite3_stmt *pStmt;
 -      rc = sqlite3_prepare_v2(p->db,
 -               "SELECT rowid FROM sqlite_schema"
 -               " WHERE name GLOB 'sqlite_stat[134]'",
 -               -1, &pStmt, 0);
 -      doStats = sqlite3_step(pStmt)==SQLITE_ROW;
 -      sqlite3_finalize(pStmt);
 -    }
 -    if( doStats==0 ){
 -      raw_printf(p->out, "/* No STAT tables available */\n");
 -    }else{
 -      raw_printf(p->out, "ANALYZE sqlite_schema;\n");
 -      data.cMode = data.mode = MODE_Insert;
 -      data.zDestTable = "sqlite_stat1";
 -      shell_exec(&data, "SELECT * FROM sqlite_stat1", 0);
 -      data.zDestTable = "sqlite_stat4";
 -      shell_exec(&data, "SELECT * FROM sqlite_stat4", 0);
 -      raw_printf(p->out, "ANALYZE sqlite_schema;\n");
 -    }
 -  }else
 -
 -  if( c=='h' && cli_strncmp(azArg[0], "headers", n)==0 ){
 -    if( nArg==2 ){
 -      p->showHeader = booleanValue(azArg[1]);
 -      p->shellFlgs |= SHFLG_HeaderSet;
 -    }else{
 -      raw_printf(stderr, "Usage: .headers on|off\n");
 -      rc = 1;
 -    }
 -  }else
 -
 -  if( c=='h' && cli_strncmp(azArg[0], "help", n)==0 ){
 -    if( nArg>=2 ){
 -      n = showHelp(p->out, azArg[1]);
 -      if( n==0 ){
 -        utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
 +    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
 +      const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
 +      if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
 +        const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
 +        char *zPrint = shellMPrintf(&rc,
 +          "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
 +          zName, zName, zSql
 +        );
 +        raw_printf(out, "%s;\n", zPrint);
 +        sqlite3_free(zPrint);
 +      }else{
 +        raw_printf(out, "%s;\n", zSql);
        }
 -    }else{
 -      showHelp(p->out, 0);
      }
 -  }else
 +    shellFinalize(&rc, pStmt);
 +  }
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){
 -    char *zTable = 0;           /* Insert data into this table */
 -    char *zSchema = 0;          /* within this schema (may default to "main") */
 -    char *zFile = 0;            /* Name of file to extra content from */
 -    sqlite3_stmt *pStmt = NULL; /* A statement */
 -    int nCol;                   /* Number of columns in the table */
 -    int nByte;                  /* Number of bytes in an SQL string */
 -    int i, j;                   /* Loop counters */
 -    int needCommit;             /* True to COMMIT or ROLLBACK at end */
 -    int nSep;                   /* Number of bytes in p->colSeparator[] */
 -    char *zSql;                 /* An SQL statement */
 -    char *zFullTabName;         /* Table name with schema if applicable */
 -    ImportCtx sCtx;             /* Reader context */
 -    char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
 -    int eVerbose = 0;           /* Larger for more console output */
 -    int nSkip = 0;              /* Initial lines to skip */
 -    int useOutputMode = 1;      /* Use output mode to determine separators */
 -    char *zCreate = 0;          /* CREATE TABLE statement text */
 -
 -    failIfSafeMode(p, "cannot run .import in safe mode");
 -    memset(&sCtx, 0, sizeof(sCtx));
 -    if( p->mode==MODE_Ascii ){
 -      xRead = ascii_read_one_field;
 -    }else{
 -      xRead = csv_read_one_field;
 +  if( rc==SQLITE_OK ){
 +    raw_printf(out, "PRAGMA writable_schema = off;\n");
 +    raw_printf(out, "COMMIT;\n");
 +  }
 +  sqlite3_exec(db, "DETACH recovery", 0, 0, 0);
 +  return rc;
 +}
 +
 +DISPATCHABLE_COMMAND( restore ? 2 3 ){
 +  int rc;
 +  const char *zSrcFile;
 +  const char *zDb;
 +  sqlite3 *pSrc;
 +  sqlite3_backup *pBackup;
 +  int nTimeout = 0;
 +
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  if( nArg==2 ){
 +    zSrcFile = azArg[1];
 +    zDb = "main";
 +  }else if( nArg==3 ){
 +    zSrcFile = azArg[2];
 +    zDb = azArg[1];
 +  }else{
 +    return DCR_TooMany;
 +  }
 +  rc = sqlite3_open(zSrcFile, &pSrc);
 +  if( rc!=SQLITE_OK ){
 +    *pzErr = smprintf("cannot open \"%s\"\n", zSrcFile);
 +    close_db(pSrc);
 +    return DCR_Error;
 +  }
 +  open_db(p, 0);
 +  pBackup = sqlite3_backup_init(DBX(p), zDb, pSrc, "main");
 +  if( pBackup==0 ){
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    close_db(pSrc);
 +    return DCR_Error;
 +  }
 +  while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
 +         || rc==SQLITE_BUSY  ){
 +    if( rc==SQLITE_BUSY ){
 +      if( nTimeout++ >= 3 ) break;
 +      sqlite3_sleep(100);
      }
 +  }
 +  sqlite3_backup_finish(pBackup);
 +  if( rc==SQLITE_DONE ){
 +    rc = 0;
 +  }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
 +    *pzErr = smprintf("source database is busy\n");
      rc = 1;
 -    for(i=1; i<nArg; i++){
 -      char *z = azArg[i];
 -      if( z[0]=='-' && z[1]=='-' ) z++;
 -      if( z[0]!='-' ){
 -        if( zFile==0 ){
 -          zFile = z;
 -        }else if( zTable==0 ){
 -          zTable = z;
 -        }else{
 -          utf8_printf(p->out, "ERROR: extra argument: \"%s\".  Usage:\n", z);
 -          showHelp(p->out, "import");
 -          goto meta_command_exit;
 -        }
 -      }else if( cli_strcmp(z,"-v")==0 ){
 -        eVerbose++;
 -      }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
 -        zSchema = azArg[++i];
 -      }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
 -        nSkip = integerValue(azArg[++i]);
 -      }else if( cli_strcmp(z,"-ascii")==0 ){
 -        sCtx.cColSep = SEP_Unit[0];
 -        sCtx.cRowSep = SEP_Record[0];
 -        xRead = ascii_read_one_field;
 -        useOutputMode = 0;
 -      }else if( cli_strcmp(z,"-csv")==0 ){
 -        sCtx.cColSep = ',';
 -        sCtx.cRowSep = '\n';
 -        xRead = csv_read_one_field;
 -        useOutputMode = 0;
 -      }else{
 -        utf8_printf(p->out, "ERROR: unknown option: \"%s\".  Usage:\n", z);
 -        showHelp(p->out, "import");
 -        goto meta_command_exit;
 -      }
 -    }
 -    if( zTable==0 ){
 -      utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n",
 -                  zFile==0 ? "FILE" : "TABLE");
 -      showHelp(p->out, "import");
 -      goto meta_command_exit;
 -    }
 -    seenInterrupt = 0;
 -    open_db(p, 0);
 -    if( useOutputMode ){
 -      /* If neither the --csv or --ascii options are specified, then set
 -      ** the column and row separator characters from the output mode. */
 -      nSep = strlen30(p->colSeparator);
 -      if( nSep==0 ){
 -        raw_printf(stderr,
 -                   "Error: non-null column separator required for import\n");
 -        goto meta_command_exit;
 -      }
 -      if( nSep>1 ){
 -        raw_printf(stderr,
 -              "Error: multi-character column separators not allowed"
 -              " for import\n");
 -        goto meta_command_exit;
 -      }
 -      nSep = strlen30(p->rowSeparator);
 -      if( nSep==0 ){
 -        raw_printf(stderr,
 -            "Error: non-null row separator required for import\n");
 -        goto meta_command_exit;
 -      }
 -      if( nSep==2 && p->mode==MODE_Csv
 -       && cli_strcmp(p->rowSeparator,SEP_CrLf)==0
 -      ){
 -        /* When importing CSV (only), if the row separator is set to the
 -        ** default output row separator, change it to the default input
 -        ** row separator.  This avoids having to maintain different input
 -        ** and output row separators. */
 -        sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
 -        nSep = strlen30(p->rowSeparator);
 -      }
 -      if( nSep>1 ){
 -        raw_printf(stderr, "Error: multi-character row separators not allowed"
 -                           " for import\n");
 -        goto meta_command_exit;
 -      }
 -      sCtx.cColSep = (u8)p->colSeparator[0];
 -      sCtx.cRowSep = (u8)p->rowSeparator[0];
 -    }
 -    sCtx.zFile = zFile;
 -    sCtx.nLine = 1;
 -    if( sCtx.zFile[0]=='|' ){
 -#ifdef SQLITE_OMIT_POPEN
 -      raw_printf(stderr, "Error: pipes are not supported in this OS\n");
 -      goto meta_command_exit;
 -#else
 -      sCtx.in = popen(sCtx.zFile+1, "r");
 -      sCtx.zFile = "<pipe>";
 -      sCtx.xCloser = pclose;
 +  }else{
 +    *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
 +    rc = 1;
 +  }
 +  close_db(pSrc);
 +  return DCR_Ok|rc;
 +}
 +
 +/*****************
 + * The .scanstats and .schema commands
 + */
 +COLLECT_HELP_TEXT[
 +  ".scanstats on|off|est    Turn sqlite3_stmt_scanstatus() metrics on or off",
 +  ".schema ?PATTERN?        Show the CREATE statements matching PATTERN",
 +  "   Options:",
 +  "      --indent             Try to pretty-print the schema",
 +  "      --nosys              Omit objects whose names start with \"sqlite_\"",
 +];
 +DISPATCHABLE_COMMAND( scanstats ? 2 2 ){
 +  ISS(p)->scanstatsOn = (u8)booleanValue(azArg[1]);
 +  if( cli_strcmp(azArg[1], "est")==0 ){
 +    p->scanstatsOn = 2;
 +  }else{
 +    p->scanstatsOn = (u8)booleanValue(azArg[1]);
 +  }
++  open_db(p, 0);
++  sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS,
++                    p->scanstatsOn, (int*)0);
 +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
 +  raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n");
  #endif
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( schema ? 1 2 ){
 +  int rc = 0;
 +  ShellText sSelect;
 +  ShellInState data;
 +  ShellExState datax;
 +  char *zErrMsg = 0;
 +  const char *zDiv = "(";
 +  const char *zName = 0;
 +  int iSchema = 0;
 +  int bDebug = 0;
 +  int bNoSystemTabs = 0;
 +  int ii;
 +
 +  open_db(p, 0);
 +  /* Consider some refactoring to avoid duplicative wholesale copying. */
 +  memcpy(&data, ISS(p), sizeof(data));
 +  memcpy(&datax, p, sizeof(datax));
 +  data.pSXS = &datax;
 +  datax.pSIS = &data;
 +
 +  data.showHeader = 0;
 +  data.cMode = data.mode = MODE_Semi;
 +  initText(&sSelect);
 +  for(ii=1; ii<nArg; ii++){
 +    if( optionMatch(azArg[ii],"indent") ){
 +      data.cMode = data.mode = MODE_Pretty;
 +    }else if( optionMatch(azArg[ii],"debug") ){
 +      bDebug = 1;
 +    }else if( optionMatch(azArg[ii],"nosys") ){
 +      bNoSystemTabs = 1;
 +    }else if( azArg[ii][0]=='-' ){
 +      return DCR_Unknown|ii;
 +    }else if( zName==0 ){
 +      zName = azArg[ii];
      }else{
 -      sCtx.in = fopen(sCtx.zFile, "rb");
 -      sCtx.xCloser = fclose;
 -    }
 -    if( sCtx.in==0 ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
 -      goto meta_command_exit;
 -    }
 -    if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
 -      char zSep[2];
 -      zSep[1] = 0;
 -      zSep[0] = sCtx.cColSep;
 -      utf8_printf(p->out, "Column separator ");
 -      output_c_string(p->out, zSep);
 -      utf8_printf(p->out, ", row separator ");
 -      zSep[0] = sCtx.cRowSep;
 -      output_c_string(p->out, zSep);
 -      utf8_printf(p->out, "\n");
 -    }
 -    sCtx.z = sqlite3_malloc64(120);
 -    if( sCtx.z==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 -    }
 -    /* Below, resources must be freed before exit. */
 -    while( (nSkip--)>0 ){
 -      while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
 -    }
 -    if( zSchema!=0 ){
 -      zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable);
 -    }else{
 -      zFullTabName = sqlite3_mprintf("\"%w\"", zTable);
 -    }
 -    zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName);
 -    if( zSql==0 || zFullTabName==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 -    }
 -    nByte = strlen30(zSql);
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
 -    if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
 -      sqlite3 *dbCols = 0;
 -      char *zRenames = 0;
 -      char *zColDefs;
 -      zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName);
 -      while( xRead(&sCtx) ){
 -        zAutoColumn(sCtx.z, &dbCols, 0);
 -        if( sCtx.cTerm!=sCtx.cColSep ) break;
 -      }
 -      zColDefs = zAutoColumn(0, &dbCols, &zRenames);
 -      if( zRenames!=0 ){
 -        utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
 -                    "Columns renamed during .import %s due to duplicates:\n"
 -                    "%s\n", sCtx.zFile, zRenames);
 -        sqlite3_free(zRenames);
 -      }
 -      assert(dbCols==0);
 -      if( zColDefs==0 ){
 -        utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
 -      import_fail:
 -        sqlite3_free(zCreate);
 -        sqlite3_free(zSql);
 -        sqlite3_free(zFullTabName);
 -        import_cleanup(&sCtx);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
 -      if( eVerbose>=1 ){
 -        utf8_printf(p->out, "%s\n", zCreate);
 -      }
 -      rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
 -      if( rc ){
 -        utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
 -        goto import_fail;
 -      }
 -      sqlite3_free(zCreate);
 -      zCreate = 0;
 -      rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    }
 -    if( rc ){
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
 -      goto import_fail;
 -    }
 -    sqlite3_free(zSql);
 -    nCol = sqlite3_column_count(pStmt);
 -    sqlite3_finalize(pStmt);
 -    pStmt = 0;
 -    if( nCol==0 ) return 0; /* no columns, no error */
 -    zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
 -    if( zSql==0 ){
 -      import_cleanup(&sCtx);
 -      shell_out_of_memory();
 -    }
 -    sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
 -    j = strlen30(zSql);
 -    for(i=1; i<nCol; i++){
 -      zSql[j++] = ',';
 -      zSql[j++] = '?';
 -    }
 -    zSql[j++] = ')';
 -    zSql[j] = 0;
 -    if( eVerbose>=2 ){
 -      utf8_printf(p->out, "Insert using: %s\n", zSql);
 -    }
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 +      return DCR_TooMany;
 +    }
 +  }
 +  if( zName!=0 ){
 +    int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0
 +      || sqlite3_strlike(zName, "sqlite_schema", '\\')==0
 +      || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0
 +      || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0;
 +    if( isSchema ){
 +      char *new_argv[2], *new_colv[2];
 +      new_argv[0] = smprintf(
 +                             "CREATE TABLE %s (\n"
 +                             "  type text,\n"
 +                             "  name text,\n"
 +                             "  tbl_name text,\n"
 +                             "  rootpage integer,\n"
 +                             "  sql text\n"
 +                             ")", zName);
 +      shell_check_oom(new_argv[0]);
 +      new_argv[1] = 0;
 +      new_colv[0] = "sql";
 +      new_colv[1] = 0;
 +      callback(&datax, 1, new_argv, new_colv);
 +      sqlite3_free(new_argv[0]);
 +    }
 +  }
 +  if( zDiv ){
 +    sqlite3_stmt *pStmt = 0;
 +    rc = sqlite3_prepare_v2(datax.dbUser,
 +                            "SELECT name FROM pragma_database_list",
 +                            -1, &pStmt, 0);
      if( rc ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      if (pStmt) sqlite3_finalize(pStmt);
 -      goto import_fail;
 -    }
 -    sqlite3_free(zSql);
 -    sqlite3_free(zFullTabName);
 -    needCommit = sqlite3_get_autocommit(p->db);
 -    if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
 -    do{
 -      int startLine = sCtx.nLine;
 -      for(i=0; i<nCol; i++){
 -        char *z = xRead(&sCtx);
 -        /*
 -        ** Did we reach end-of-file before finding any columns?
 -        ** If so, stop instead of NULL filling the remaining columns.
 -        */
 -        if( z==0 && i==0 ) break;
 -        /*
 -        ** Did we reach end-of-file OR end-of-line before finding any
 -        ** columns in ASCII mode?  If so, stop instead of NULL filling
 -        ** the remaining columns.
 -        */
 -        if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
 -        sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
 -        if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
 -          utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
 -                          "filling the rest with NULL\n",
 -                          sCtx.zFile, startLine, nCol, i+1);
 -          i += 2;
 -          while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
 -        }
 -      }
 -      if( sCtx.cTerm==sCtx.cColSep ){
 -        do{
 -          xRead(&sCtx);
 -          i++;
 -        }while( sCtx.cTerm==sCtx.cColSep );
 -        utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
 -                        "extras ignored\n",
 -                        sCtx.zFile, startLine, nCol, i);
 -      }
 -      if( i>=nCol ){
 -        sqlite3_step(pStmt);
 -        rc = sqlite3_reset(pStmt);
 -        if( rc!=SQLITE_OK ){
 -          utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
 -                      startLine, sqlite3_errmsg(p->db));
 -          sCtx.nErr++;
 -        }else{
 -          sCtx.nRow++;
 -        }
 -      }
 -    }while( sCtx.cTerm!=EOF );
 -
 -    import_cleanup(&sCtx);
 -    sqlite3_finalize(pStmt);
 -    if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
 -    if( eVerbose>0 ){
 -      utf8_printf(p->out,
 -          "Added %d rows with %d errors using %d lines of input\n",
 -          sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
 -    }
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 -
 -#ifndef SQLITE_UNTESTABLE
 -  if( c=='i' && cli_strncmp(azArg[0], "imposter", n)==0 ){
 -    char *zSql;
 -    char *zCollist = 0;
 -    sqlite3_stmt *pStmt;
 -    int tnum = 0;
 -    int isWO = 0;  /* True if making an imposter of a WITHOUT ROWID table */
 -    int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
 -    int i;
 -    if( !ShellHasFlag(p,SHFLG_TestingMode) ){
 -      utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
 -                  "imposter");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
 -      utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n"
 -                          "       .imposter off\n");
 -      /* Also allowed, but not documented:
 -      **
 -      **    .imposter TABLE IMPOSTER
 -      **
 -      ** where TABLE is a WITHOUT ROWID table.  In that case, the
 -      ** imposter is another WITHOUT ROWID table with the columns in
 -      ** storage order. */
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    if( nArg==2 ){
 -      sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
 -      goto meta_command_exit;
 -    }
 -    zSql = sqlite3_mprintf(
 -      "SELECT rootpage, 0 FROM sqlite_schema"
 -      " WHERE name='%q' AND type='index'"
 -      "UNION ALL "
 -      "SELECT rootpage, 1 FROM sqlite_schema"
 -      " WHERE name='%q' AND type='table'"
 -      "   AND sql LIKE '%%without%%rowid%%'",
 -      azArg[1], azArg[1]
 -    );
 -    sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    sqlite3_free(zSql);
 -    if( sqlite3_step(pStmt)==SQLITE_ROW ){
 -      tnum = sqlite3_column_int(pStmt, 0);
 -      isWO = sqlite3_column_int(pStmt, 1);
 +      *pzErr = smprintf("%s\n", sqlite3_errmsg(datax.dbUser));
 +      sqlite3_finalize(pStmt);
 +      return DCR_Error;
      }
 -    sqlite3_finalize(pStmt);
 -    zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    sqlite3_free(zSql);
 -    i = 0;
 -    while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 -      char zLabel[20];
 -      const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
 -      i++;
 -      if( zCol==0 ){
 -        if( sqlite3_column_int(pStmt,1)==-1 ){
 -          zCol = "_ROWID_";
 -        }else{
 -          sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
 -          zCol = zLabel;
 -        }
 -      }
 -      if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
 -        lenPK = (int)strlen(zCollist);
 -      }
 -      if( zCollist==0 ){
 -        zCollist = sqlite3_mprintf("\"%w\"", zCol);
 +    appendText(&sSelect, "SELECT sql FROM", 0);
 +    iSchema = 0;
 +    while( sqlite3_step(pStmt)==SQLITE_ROW ){
 +      const char *zDb = (const char*)sqlite3_column_text(pStmt, 0);
 +      char zScNum[30];
 +      sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema);
 +      appendText(&sSelect, zDiv, 0);
 +      zDiv = " UNION ALL ";
 +      appendText(&sSelect, "SELECT shell_add_schema(sql,", 0);
 +      if( sqlite3_stricmp(zDb, "main")!=0 ){
 +        appendText(&sSelect, zDb, '\'');
        }else{
 -        zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol);
 +        appendText(&sSelect, "NULL", 0);
        }
 +      appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0);
 +      appendText(&sSelect, zScNum, 0);
 +      appendText(&sSelect, " AS snum, ", 0);
 +      appendText(&sSelect, zDb, '\'');
 +      appendText(&sSelect, " AS sname FROM ", 0);
 +      appendText(&sSelect, zDb, quoteChar(zDb));
 +      appendText(&sSelect, ".sqlite_schema", 0);
      }
      sqlite3_finalize(pStmt);
 -    if( i==0 || tnum==0 ){
 -      utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]);
 -      rc = 1;
 -      sqlite3_free(zCollist);
 -      goto meta_command_exit;
 +#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
 +    if( zName ){
 +      appendText(&sSelect,
 +                 " UNION ALL SELECT shell_module_schema(name),"
 +                 " 'table', name, name, name, 9e+99, 'main'"
 +                 " FROM pragma_module_list",
 +                 0);
      }
 -    if( lenPK==0 ) lenPK = 100000;
 -    zSql = sqlite3_mprintf(
 -          "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID",
 -          azArg[2], zCollist, lenPK, zCollist);
 -    sqlite3_free(zCollist);
 -    rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum);
 -    if( rc==SQLITE_OK ){
 -      rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
 -      sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0);
 -      if( rc ){
 -        utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db));
 +#endif
 +    appendText(&sSelect, ") WHERE ", 0);
 +    if( zName ){
 +      char *zQarg = smprintf("%Q", zName);
 +      int bGlob;
 +      shell_check_oom(zQarg);
 +      bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0
 +        || strchr(zName, '[') != 0;
 +      if( strchr(zName, '.') ){
 +        appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0);
        }else{
 -        utf8_printf(stdout, "%s;\n", zSql);
 -        raw_printf(stdout,
 -          "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n",
 -          azArg[1], isWO ? "table" : "index"
 -        );
 +        appendText(&sSelect, "lower(tbl_name)", 0);
        }
 -    }else{
 -      raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
 -      rc = 1;
 -    }
 -    sqlite3_free(zSql);
 -  }else
 -#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */
 -
 -#ifdef SQLITE_ENABLE_IOTRACE
 -  if( c=='i' && cli_strncmp(azArg[0], "iotrace", n)==0 ){
 -    SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
 -    if( iotrace && iotrace!=stdout ) fclose(iotrace);
 -    iotrace = 0;
 -    if( nArg<2 ){
 -      sqlite3IoTrace = 0;
 -    }else if( cli_strcmp(azArg[1], "-")==0 ){
 -      sqlite3IoTrace = iotracePrintf;
 -      iotrace = stdout;
 -    }else{
 -      iotrace = fopen(azArg[1], "w");
 -      if( iotrace==0 ){
 -        utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
 -        sqlite3IoTrace = 0;
 -        rc = 1;
 -      }else{
 -        sqlite3IoTrace = iotracePrintf;
 +      appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
 +      appendText(&sSelect, zQarg, 0);
 +      if( !bGlob ){
 +        appendText(&sSelect, " ESCAPE '\\' ", 0);
        }
 +      appendText(&sSelect, " AND ", 0);
 +      sqlite3_free(zQarg);
      }
 -  }else
 -#endif
 -
 -  if( c=='l' && n>=5 && cli_strncmp(azArg[0], "limits", n)==0 ){
 -    static const struct {
 -       const char *zLimitName;   /* Name of a limit */
 -       int limitCode;            /* Integer code for that limit */
 -    } aLimit[] = {
 -      { "length",                SQLITE_LIMIT_LENGTH                    },
 -      { "sql_length",            SQLITE_LIMIT_SQL_LENGTH                },
 -      { "column",                SQLITE_LIMIT_COLUMN                    },
 -      { "expr_depth",            SQLITE_LIMIT_EXPR_DEPTH                },
 -      { "compound_select",       SQLITE_LIMIT_COMPOUND_SELECT           },
 -      { "vdbe_op",               SQLITE_LIMIT_VDBE_OP                   },
 -      { "function_arg",          SQLITE_LIMIT_FUNCTION_ARG              },
 -      { "attached",              SQLITE_LIMIT_ATTACHED                  },
 -      { "like_pattern_length",   SQLITE_LIMIT_LIKE_PATTERN_LENGTH       },
 -      { "variable_number",       SQLITE_LIMIT_VARIABLE_NUMBER           },
 -      { "trigger_depth",         SQLITE_LIMIT_TRIGGER_DEPTH             },
 -      { "worker_threads",        SQLITE_LIMIT_WORKER_THREADS            },
 -    };
 -    int i, n2;
 -    open_db(p, 0);
 -    if( nArg==1 ){
 -      for(i=0; i<ArraySize(aLimit); i++){
 -        printf("%20s %d\n", aLimit[i].zLimitName,
 -               sqlite3_limit(p->db, aLimit[i].limitCode, -1));
 -      }
 -    }else if( nArg>3 ){
 -      raw_printf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +    if( bNoSystemTabs ){
 +      appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
 +    }
 +    appendText(&sSelect, "sql IS NOT NULL"
 +               " ORDER BY snum, rowid", 0);
 +    if( bDebug ){
 +      utf8_printf(data.out, "SQL: %s;\n", sSelect.z);
      }else{
 -      int iLimit = -1;
 -      n2 = strlen30(azArg[1]);
 -      for(i=0; i<ArraySize(aLimit); i++){
 -        if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
 -          if( iLimit<0 ){
 -            iLimit = i;
 -          }else{
 -            utf8_printf(stderr, "ambiguous limit: \"%s\"\n", azArg[1]);
 -            rc = 1;
 -            goto meta_command_exit;
 -          }
 -        }
 -      }
 -      if( iLimit<0 ){
 -        utf8_printf(stderr, "unknown limit: \"%s\"\n"
 -                        "enter \".limits\" with no arguments for a list.\n",
 -                         azArg[1]);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      if( nArg==3 ){
 -        sqlite3_limit(p->db, aLimit[iLimit].limitCode,
 -                      (int)integerValue(azArg[2]));
 -      }
 -      printf("%20s %d\n", aLimit[iLimit].zLimitName,
 -             sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
 +      rc = sqlite3_exec(datax.dbUser, sSelect.z, callback, &datax, &zErrMsg);
      }
 -  }else
 -
 -  if( c=='l' && n>2 && cli_strncmp(azArg[0], "lint", n)==0 ){
 -    open_db(p, 0);
 -    lintDotCommand(p, azArg, nArg);
 -  }else
 +    freeText(&sSelect);
 +  }
 +  if( zErrMsg ){
 +    *pzErr = zErrMsg;
 +    return DCR_Error;
 +  }else if( rc != SQLITE_OK ){
 +    *pzErr = smprintf("Error: querying schema information\n");
 +    return DCR_Error;
 +  }else{
 +    return DCR_Ok;
 +  }
 +}
  
 -#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
 -  if( c=='l' && cli_strncmp(azArg[0], "load", n)==0 ){
 -    const char *zFile, *zProc;
 -    char *zErrMsg = 0;
 -    failIfSafeMode(p, "cannot run .load in safe mode");
 -    if( nArg<2 || azArg[1][0]==0 ){
 -      /* Must have a non-empty FILE. (Will not load self.) */
 -      raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    zFile = azArg[1];
 -    zProc = nArg>=3 ? azArg[2] : 0;
 -    open_db(p, 0);
 -    rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: %s\n", zErrMsg);
 -      sqlite3_free(zErrMsg);
 -      rc = 1;
 +/*****************
 + * The .selecttrace, .separator, .session and .sha3sum commands
 + */
 +CONDITION_COMMAND( session defined(SQLITE_ENABLE_SESSION) );
 +COLLECT_HELP_TEXT[
 +  ".separator COL ?ROW?     Change the column and row separators",
 +  ".session ?NAME? CMD ...  Create or control sessions",
 +  "   Subcommands:",
 +  "     attach TABLE             Attach TABLE",
 +  "     changeset FILE           Write a changeset into FILE",
 +  "     close                    Close one session",
 +  "     enable ?BOOLEAN?         Set or query the enable bit",
 +  "     filter GLOB...           Reject tables matching GLOBs",
 +  "     indirect ?BOOLEAN?       Mark or query the indirect status",
 +  "     isempty                  Query whether the session is empty",
 +  "     list                     List currently open session names",
 +  "     open DB NAME             Open a new session on DB",
 +  "     patchset FILE            Write a patchset into FILE",
 +  "   If ?NAME? is omitted, the first defined session is used.",
 +  ".sha3sum ...             Compute a SHA3 hash of database content",
 +  "    Options:",
 +  "      --schema              Also hash the sqlite_schema table",
 +  "      --sha3-224            Use the sha3-224 algorithm",
 +  "      --sha3-256            Use the sha3-256 algorithm (default)",
 +  "      --sha3-384            Use the sha3-384 algorithm",
 +  "      --sha3-512            Use the sha3-512 algorithm",
 +  "    Any other argument is a LIKE pattern for tables to hash",
 +];
 +DISPATCHABLE_COMMAND( selecttrace ? 1 0 ){
 +  unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 +  sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( separator ? 2 3 ){
 +  if( nArg>=2 ){
 +    sqlite3_snprintf(sizeof(ISS(p)->colSeparator), ISS(p)->colSeparator,
 +                     "%.*s", (int)ArraySize(ISS(p)->colSeparator)-1, azArg[1]);
 +  }
 +  if( nArg>=3 ){
 +    sqlite3_snprintf(sizeof(ISS(p)->rowSeparator), ISS(p)->rowSeparator,
 +                     "%.*s", (int)ArraySize(ISS(p)->rowSeparator)-1, azArg[2]);
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( session 3 2 0 ){
 +  int rc = 0;
 +  struct AuxDb *pAuxDb = ISS(p)->pAuxDb;
 +  OpenSession *pSession = &pAuxDb->aSession[0];
 +  FILE *out = ISS(p)->out;
 +  char **azCmd = &azArg[1];
 +  int iSes = 0;
 +  int nCmd = nArg - 1;
 +  int i;
 +  open_db(p, 0);
 +  if( nArg>=3 ){
 +    for(iSes=0; iSes<pAuxDb->nSession; iSes++){
 +      if( cli_strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
 +    }
 +    if( iSes<pAuxDb->nSession ){
 +      pSession = &pAuxDb->aSession[iSes];
 +      azCmd++;
 +      nCmd--;
 +    }else{
 +      pSession = &pAuxDb->aSession[0];
 +      iSes = 0;
      }
 -  }else
 -#endif
 +  }
  
 -  if( c=='l' && cli_strncmp(azArg[0], "log", n)==0 ){
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .log FILENAME\n");
 -      rc = 1;
 +  /* .session attach TABLE
 +  ** Invoke the sqlite3session_attach() interface to attach a particular
 +  ** table so that it is never filtered.
 +  */
 +  if( cli_strcmp(azCmd[0],"attach")==0 ){
 +    if( nCmd!=2 ) goto session_syntax_error;
 +    if( pSession->p==0 ){
 +    session_not_open:
 +      raw_printf(STD_ERR, "ERROR: No sessions are open\n");
      }else{
 -      const char *zFile = azArg[1];
 -      if( p->bSafeMode
 -       && cli_strcmp(zFile,"on")!=0
 -       && cli_strcmp(zFile,"off")!=0
 -      ){
 -        raw_printf(stdout, "cannot set .log to anything other "
 -                   "than \"on\" or \"off\"\n");
 -        zFile = "off";
 +      rc = sqlite3session_attach(pSession->p, azCmd[1]);
 +      if( rc ){
 +        raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc);
 +        rc = 0;
        }
 -      output_file_close(p->pLog);
 -      if( cli_strcmp(zFile,"on")==0 ) zFile = "stdout";
 -      p->pLog = output_file_open(zFile, 0);
      }
    }else
  
      }
    }else
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( (c=='o'
 -        && (cli_strncmp(azArg[0], "output", n)==0
 -            || cli_strncmp(azArg[0], "once", n)==0))
 -   || (c=='e' && n==5 && cli_strcmp(azArg[0],"excel")==0)
 -  ){
 -    char *zFile = 0;
 -    int bTxtMode = 0;
 -    int i;
 -    int eMode = 0;
 -    int bOnce = 0;            /* 0: .output, 1: .once, 2: .excel */
 -    unsigned char zBOM[4];    /* Byte-order mark to using if --bom is present */
 -
 -    zBOM[0] = 0;
 -    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 -    if( c=='e' ){
 -      eMode = 'x';
 -      bOnce = 2;
 -    }else if( cli_strncmp(azArg[0],"once",n)==0 ){
 -      bOnce = 1;
 +  /* .session isempty
 +  ** Determine if the session is empty
 +  */
 +  if( cli_strcmp(azCmd[0], "isempty")==0 ){
 +    int ii;
 +    if( nCmd!=1 ) goto session_syntax_error;
 +    if( pAuxDb->nSession ){
 +      ii = sqlite3session_isempty(pSession->p);
 +      utf8_printf(out, "session %s isempty flag = %d\n",
 +                  pSession->zName, ii);
      }
 -    for(i=1; i<nArg; i++){
 -      char *z = azArg[i];
 -      if( z[0]=='-' ){
 -        if( z[1]=='-' ) z++;
 -        if( cli_strcmp(z,"-bom")==0 ){
 -          zBOM[0] = 0xef;
 -          zBOM[1] = 0xbb;
 -          zBOM[2] = 0xbf;
 -          zBOM[3] = 0;
 -        }else if( c!='e' && cli_strcmp(z,"-x")==0 ){
 -          eMode = 'x';  /* spreadsheet */
 -        }else if( c!='e' && cli_strcmp(z,"-e")==0 ){
 -          eMode = 'e';  /* text editor */
 -        }else{
 -          utf8_printf(p->out, "ERROR: unknown option: \"%s\".  Usage:\n",
 -                      azArg[i]);
 -          showHelp(p->out, azArg[0]);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -      }else if( zFile==0 && eMode!='e' && eMode!='x' ){
 -        zFile = sqlite3_mprintf("%s", z);
 -        if( zFile && zFile[0]=='|' ){
 -          while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]);
 -          break;
 -        }
 -      }else{
 -        utf8_printf(p->out,"ERROR: extra parameter: \"%s\".  Usage:\n",
 -                    azArg[i]);
 -        showHelp(p->out, azArg[0]);
 -        rc = 1;
 -        sqlite3_free(zFile);
 -        goto meta_command_exit;
 -      }
 +  }else
 +
 +  /* .session list
 +  ** List all currently open sessions
 +  */
 +  if( cli_strcmp(azCmd[0],"list")==0 ){
 +    for(i=0; i<pAuxDb->nSession; i++){
 +      utf8_printf(out, "%d %s\n", i, pAuxDb->aSession[i].zName);
      }
 -    if( zFile==0 ){
 -      zFile = sqlite3_mprintf("stdout");
 +  }else
 +
 +  /* .session open DB NAME
 +  ** Open a new session called NAME on the attached database DB.
 +  ** DB is normally "main".
 +  */
 +  if( cli_strcmp(azCmd[0],"open")==0 ){
 +    char *zName;
 +    if( nCmd!=3 ) goto session_syntax_error;
 +    zName = azCmd[2];
 +    if( zName[0]==0 ) goto session_syntax_error;
 +    for(i=0; i<pAuxDb->nSession; i++){
 +      if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
 +        utf8_printf(STD_ERR, "Session \"%s\" already exists\n", zName);
 +        return rc;
 +      }
 +    }
 +    if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
 +      raw_printf
 +        (STD_ERR, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
 +      return rc;
 +    }
 +    pSession = &pAuxDb->aSession[pAuxDb->nSession];
 +    rc = sqlite3session_create(DBX(p), azCmd[1], &pSession->p);
 +    if( rc ){
 +      *pzErr = smprintf("Cannot open session: error code=%d\n", rc);
 +      return rc;
      }
 -    if( bOnce ){
 -      p->outCount = 2;
 +    pSession->nFilter = 0;
 +    sqlite3session_table_filter(pSession->p, session_filter, pSession);
 +    pAuxDb->nSession++;
 +    shell_newstr_assign(&pSession->zName, smprintf("%s", zName));
 +  }else{
 +
 +  /* If no command name matches, show a syntax error */
 +  session_syntax_error:
 +    showHelp(out, "session", p);
 +    return DCR_CmdErred;
 +  }
 +  return rc;
 +}
 +DISPATCHABLE_COMMAND( sha3sum 4 1 1 ){
 +  const char *zLike = 0;   /* Which table to checksum. 0 means everything */
 +  int i;                   /* Loop counter */
 +  int bSchema = 0;         /* Also hash the schema */
 +  int bSeparate = 0;       /* Hash each table separately */
 +  int iSize = 224;         /* Hash algorithm to use */
 +  int bDebug = 0;          /* Only show the query that would have run */
 +  sqlite3_stmt *pStmt;     /* For querying tables names */
 +  char *zSql;              /* SQL to be run */
 +  char *zSep;              /* Separator */
 +  ShellText sSql;          /* Complete SQL for the query to run the hash */
 +  ShellText sQuery;        /* Set of queries used to read all content */
 +  open_db(p, 0);
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      z++;
 +      if( z[0]=='-' ) z++;
 +      if( cli_strcmp(z,"schema")==0 ){
 +        bSchema = 1;
 +      }else
 +        if( cli_strcmp(z,"sha3-224")==0 || cli_strcmp(z,"sha3-256")==0
 +            || cli_strcmp(z,"sha3-384")==0 || cli_strcmp(z,"sha3-512")==0
 +            ){
 +          iSize = atoi(&z[5]);
 +        }else
 +          if( cli_strcmp(z,"debug")==0 ){
 +            bDebug = 1;
 +          }else
 +            {
 +              *pzErr = smprintf("Unknown option \"%s\" on \"%s\"\n",
 +                                azArg[i], azArg[0]);
 +              return DCR_Unknown|i;
 +            }
 +    }else if( zLike ){
 +      return DCR_TooMany;
      }else{
 -      p->outCount = 0;
 -    }
 -    output_reset(p);
 -#ifndef SQLITE_NOHAVE_SYSTEM
 -    if( eMode=='e' || eMode=='x' ){
 -      p->doXdgOpen = 1;
 -      outputModePush(p);
 -      if( eMode=='x' ){
 -        /* spreadsheet mode.  Output as CSV. */
 -        newTempFile(p, "csv");
 -        ShellClearFlag(p, SHFLG_Echo);
 -        p->mode = MODE_Csv;
 -        sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
 -        sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
 -      }else{
 -        /* text editor mode */
 -        newTempFile(p, "txt");
 -        bTxtMode = 1;
 -      }
 -      sqlite3_free(zFile);
 -      zFile = sqlite3_mprintf("%s", p->zTempFile);
 +      zLike = z;
 +      bSeparate = 1;
 +      if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
      }
 -#endif /* SQLITE_NOHAVE_SYSTEM */
 -    shell_check_oom(zFile);
 -    if( zFile[0]=='|' ){
 -#ifdef SQLITE_OMIT_POPEN
 -      raw_printf(stderr, "Error: pipes are not supported in this OS\n");
 -      rc = 1;
 -      p->out = stdout;
 -#else
 -      p->out = popen(zFile + 1, "w");
 -      if( p->out==0 ){
 -        utf8_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1);
 -        p->out = stdout;
 -        rc = 1;
 +  }
 +  if( bSchema ){
 +    zSql = "SELECT lower(name) AS tname FROM sqlite_schema"
 +      " WHERE type='table' AND coalesce(rootpage,0)>1"
 +      " UNION ALL SELECT 'sqlite_schema'"
 +      " ORDER BY 1 collate nocase";
 +  }else{
 +    zSql = "SELECT lower(name) AS tname FROM sqlite_schema"
 +      " WHERE type='table' AND coalesce(rootpage,0)>1"
 +      " AND name NOT LIKE 'sqlite_%'"
 +      " ORDER BY 1 collate nocase";
 +  }
 +  sqlite3_prepare_v2(DBX(p), zSql, -1, &pStmt, 0);
 +  initText(&sQuery);
 +  initText(&sSql);
 +  appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
 +  zSep = "VALUES(";
 +  while( SQLITE_ROW==sqlite3_step(pStmt) ){
 +    const char *zTab = (const char*)sqlite3_column_text(pStmt,0);
 +    if( zTab==0 ) continue;
 +    if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue;
 +    if( cli_strncmp(zTab, "sqlite_",7)!=0 ){
 +      appendText(&sQuery,"SELECT * FROM ", 0);
 +      appendText(&sQuery,zTab,'"');
 +      appendText(&sQuery," NOT INDEXED;", 0);
 +    }else if( cli_strcmp(zTab, "sqlite_schema")==0 ){
 +      appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
 +                 " ORDER BY name;", 0);
 +    }else if( cli_strcmp(zTab, "sqlite_sequence")==0 ){
 +      appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
 +                 " ORDER BY name;", 0);
 +    }else if( cli_strcmp(zTab, "sqlite_stat1")==0 ){
 +      appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
 +                 " ORDER BY tbl,idx;", 0);
 +    }else if( cli_strcmp(zTab, "sqlite_stat4")==0 ){
 +      appendText(&sQuery, "SELECT * FROM ", 0);
 +      appendText(&sQuery, zTab, 0);
 +      appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0);
 +    }
 +    appendText(&sSql, zSep, 0);
 +    appendText(&sSql, sQuery.z, '\'');
 +    sQuery.n = 0;
 +    appendText(&sSql, ",", 0);
 +    appendText(&sSql, zTab, '\'');
 +    zSep = "),(";
 +  }
 +  sqlite3_finalize(pStmt);
 +  if( bSeparate ){
 +    zSql = smprintf(
 +           "%s))"
 +           " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label"
 +           "   FROM [sha3sum$query]",
 +           sSql.z, iSize);
 +  }else{
 +    zSql = smprintf(
 +           "%s))"
 +           " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash"
 +           "   FROM [sha3sum$query]",
 +           sSql.z, iSize);
 +  }
 +  shell_check_oom(zSql);
 +  freeText(&sQuery);
 +  freeText(&sSql);
 +  if( bDebug ){
 +    utf8_printf(ISS(p)->out, "%s\n", zSql);
 +  }else{
 +    shell_exec(p, zSql, 0);
 +  }
 +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && !defined(SQLITE_OMIT_VIRTUALTABLE)
 +  {
 +    int lrc;
 +    char *zRevText = /* Query for reversible to-blob-to-text check */
 +      "SELECT lower(name) as tname FROM sqlite_schema\n"
 +      "WHERE type='table' AND coalesce(rootpage,0)>1\n"
 +      "AND name NOT LIKE 'sqlite_%%'%s\n"
 +      "ORDER BY 1 collate nocase";
 +    zRevText = sqlite3_mprintf(zRevText, zLike? " AND name LIKE $tspec" : "");
 +    zRevText = sqlite3_mprintf(
 +        /* lower-case query is first run, producing upper-case query. */
 +        "with tabcols as materialized(\n"
 +        "select tname, cname\n"
 +        "from ("
 +        " select ss.tname as tname, ti.name as cname\n"
 +        " from (%z) ss\n inner join pragma_table_info(tname) ti))\n"
 +        "select 'SELECT total(bad_text_count) AS bad_text_count\n"
 +        "FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n"
 +        " from (select 'SELECT COUNT(*) AS bad_text_count\n"
 +        "FROM '||tname||' WHERE '\n"
 +        "||group_concat('CAST(CAST('||cname||' AS BLOB) AS TEXT)<>'||cname\n"
 +        "|| ' AND typeof('||cname||')=''text'' ',\n"
 +        "' OR ') as query, tname from tabcols group by tname)"
 +        , zRevText);
 +    shell_check_oom(zRevText);
 +    if( bDebug ) utf8_printf(p->out, "%s\n", zRevText);
 +    lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0);
-     assert(lrc==SQLITE_OK);
++    if( lrc!=SQLITE_OK ){
++      sqlite3_free(zRevText);
++      return DCR_Error;
++    }
 +    if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC);
 +    lrc = SQLITE_ROW==sqlite3_step(pStmt);
 +    if( lrc ){
 +      const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0);
 +      sqlite3_stmt *pCheckStmt;
 +      lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0);
 +      if( bDebug ) utf8_printf(p->out, "%s\n", zGenQuery);
-       if( SQLITE_OK==lrc ){
++      if( SQLITE_OK!=lrc ){
++        sqlite3_finalize(pStmt);
++        sqlite3_free(zRevText);
++        return DCR_Error;
+       }else{
 -        if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out);
 -        sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
 -      }
 -#endif
 -    }else{
 -      p->out = output_file_open(zFile, bTxtMode);
 -      if( p->out==0 ){
 -        if( cli_strcmp(zFile,"off")!=0 ){
 -          utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile);
 +        if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){
 +          double countIrreversible = sqlite3_column_double(pCheckStmt, 0);
 +          if( countIrreversible>0 ){
 +            int sz = (int)(countIrreversible + 0.5);
 +            utf8_printf(stderr,
 +               "Digest includes %d invalidly encoded text field%s.\n",
 +               sz, (sz>1)? "s": "");
 +          }
          }
 -        p->out = stdout;
 -        rc = 1;
 -      } else {
 -        if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out);
 -        sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
 +        sqlite3_finalize(pCheckStmt);
        }
 +      sqlite3_finalize(pStmt);
      }
 -    sqlite3_free(zFile);
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +    sqlite3_free(zRevText);
 +  }
 +#endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */
 +  sqlite3_free(zSql);
 +  return DCR_Ok;
 +}
  
 -  if( c=='p' && n>=3 && cli_strncmp(azArg[0], "parameter", n)==0 ){
 -    open_db(p,0);
 -    if( nArg<=1 ) goto parameter_syntax_error;
 +/*****************
 + * The .selftest* and .show commands
 + */
 +CONDITION_COMMAND( selftest_bool defined(SQLITE_DEBUG) );
 +CONDITION_COMMAND( selftest_int defined(SQLITE_DEBUG) );
 +COLLECT_HELP_TEXT[
 +  ",selftest ?OPTIONS?      Run tests defined in the SELFTEST table",
 +  "    Options:",
 +  "       --init               Create a new SELFTEST table",
 +  "       -v                   Verbose output",
 +  ",selftest_bool ?ARGS?    Show boolean values of ARGS as flag tokens",
 +  ",selftest_int ?ARGS?     Show integer values of ARGS as integer tokens",
 +  ".show                    Show the current values for various settings",
 +];
  
 -    /* .parameter clear
 -    ** Clear all bind parameters by dropping the TEMP table that holds them.
 -    */
 -    if( nArg==2 && cli_strcmp(azArg[1],"clear")==0 ){
 -      sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp.sqlite_parameters;",
 -                   0, 0, 0);
 -    }else
 +DISPATCHABLE_COMMAND( selftest_bool 10 0 0 ){
 +  int i, v;
 +  for(i=1; i<nArg; i++){
 +    v = booleanValue(azArg[i]);
 +    utf8_printf(ISS(p)->out, "%s: %d 0x%x\n", azArg[i], v, v);
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( selftest_int 10 0 0 ){
 +  int i; sqlite3_int64 v;
 +  for(i=1; i<nArg; i++){
 +    char zBuf[200];
 +    v = integerValue(azArg[i]);
 +    sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
 +    utf8_printf(ISS(p)->out, "%s", zBuf);
 +  }
 +  return DCR_Ok;
 +}
  
 -    /* .parameter list
 -    ** List all bind parameters.
 -    */
 -    if( nArg==2 && cli_strcmp(azArg[1],"list")==0 ){
 -      sqlite3_stmt *pStmt = 0;
 -      int rx;
 -      int len = 0;
 -      rx = sqlite3_prepare_v2(p->db,
 -             "SELECT max(length(key)) "
 -             "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
 -      if( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 -        len = sqlite3_column_int(pStmt, 0);
 -        if( len>40 ) len = 40;
 -      }
 -      sqlite3_finalize(pStmt);
 -      pStmt = 0;
 -      if( len ){
 -        rx = sqlite3_prepare_v2(p->db,
 -             "SELECT key, quote(value) "
 -             "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
 -        while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 -          utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
 -                      sqlite3_column_text(pStmt,1));
 -        }
 -        sqlite3_finalize(pStmt);
 -      }
 -    }else
 +DISPATCHABLE_COMMAND( selftest 4 0 0 ){
 +  int rc = 0;
 +  ShellInState *psi = ISS(p);
 +  int bIsInit = 0;         /* True to initialize the SELFTEST table */
 +  int bVerbose = 0;        /* Verbose output */
 +  int bSelftestExists;     /* True if SELFTEST already exists */
 +  int i, k;                /* Loop counters */
 +  int nTest = 0;           /* Number of tests runs */
 +  int nErr = 0;            /* Number of errors seen */
 +  ShellText str;           /* Answer for a query */
 +  sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */
  
 -    /* .parameter init
 -    ** Make sure the TEMP table used to hold bind parameters exists.
 -    ** Create it if necessary.
 -    */
 -    if( nArg==2 && cli_strcmp(azArg[1],"init")==0 ){
 -      bind_table_init(p);
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    if( cli_strcmp(z,"-init")==0 ){
 +      bIsInit = 1;
      }else
 -
 -    /* .parameter set NAME VALUE
 -    ** Set or reset a bind parameter.  NAME should be the full parameter
 -    ** name exactly as it appears in the query.  (ex: $abc, @def).  The
 -    ** VALUE can be in either SQL literal notation, or if not it will be
 -    ** understood to be a text string.
 -    */
 -    if( nArg==4 && cli_strcmp(azArg[1],"set")==0 ){
 -      int rx;
 -      char *zSql;
 -      sqlite3_stmt *pStmt;
 -      const char *zKey = azArg[2];
 -      const char *zValue = azArg[3];
 -      bind_table_init(p);
 -      zSql = sqlite3_mprintf(
 -                  "REPLACE INTO temp.sqlite_parameters(key,value)"
 -                  "VALUES(%Q,%s);", zKey, zValue);
 -      shell_check_oom(zSql);
 -      pStmt = 0;
 -      rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -      sqlite3_free(zSql);
 -      if( rx!=SQLITE_OK ){
 -        sqlite3_finalize(pStmt);
 -        pStmt = 0;
 -        zSql = sqlite3_mprintf(
 -                   "REPLACE INTO temp.sqlite_parameters(key,value)"
 -                   "VALUES(%Q,%Q);", zKey, zValue);
 -        shell_check_oom(zSql);
 -        rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -        sqlite3_free(zSql);
 -        if( rx!=SQLITE_OK ){
 -          utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
 -          sqlite3_finalize(pStmt);
 -          pStmt = 0;
 -          rc = 1;
 +      if( cli_strcmp(z,"-v")==0 ){
 +        bVerbose++;
 +      }else
 +        {
 +          *pzErr = smprintf
 +            ("Unknown option \"%s\" on \"%s\"\n"
 +             "Should be one of: --init -v\n", azArg[i], azArg[0]);
 +          return DCR_ArgWrong;
          }
 -      }
 -      sqlite3_step(pStmt);
 -      sqlite3_finalize(pStmt);
 -    }else
 -
 -    /* .parameter unset NAME
 -    ** Remove the NAME binding from the parameter binding table, if it
 -    ** exists.
 -    */
 -    if( nArg==3 && cli_strcmp(azArg[1],"unset")==0 ){
 -      char *zSql = sqlite3_mprintf(
 -          "DELETE FROM temp.sqlite_parameters WHERE key=%Q", azArg[2]);
 -      shell_check_oom(zSql);
 -      sqlite3_exec(p->db, zSql, 0, 0, 0);
 -      sqlite3_free(zSql);
 -    }else
 -    /* If no command name matches, show a syntax error */
 -    parameter_syntax_error:
 -    showHelp(p->out, "parameter");
 -  }else
 -
 -  if( c=='p' && n>=3 && cli_strncmp(azArg[0], "print", n)==0 ){
 -    int i;
 -    for(i=1; i<nArg; i++){
 -      if( i>1 ) raw_printf(p->out, " ");
 -      utf8_printf(p->out, "%s", azArg[i]);
 +  }
 +  open_db(p,0);
 +  if( sqlite3_table_column_metadata(DBX(p),"main","selftest",0,0,0,0,0,0)
 +      != SQLITE_OK ){
 +    bSelftestExists = 0;
 +  }else{
 +    bSelftestExists = 1;
 +  }
 +  if( bIsInit ){
 +    createSelftestTable(ISS(p));
 +    bSelftestExists = 1;
 +  }
 +  initText(&str);
 +  appendText(&str, "x", 0);
 +  for(k=bSelftestExists; k>=0; k--){
 +    if( k==1 ){
 +      rc = sqlite3_prepare_v2(DBX(p),
 +              "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
 +              -1, &pStmt, 0);
 +    }else{
 +      rc = sqlite3_prepare_v2(DBX(p),
 +          "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
 +          "      (1,'run','PRAGMA integrity_check','ok')",
 +          -1, &pStmt, 0);
      }
 -    raw_printf(p->out, "\n");
 -  }else
 -
 -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
 -  if( c=='p' && n>=3 && cli_strncmp(azArg[0], "progress", n)==0 ){
 -    int i;
 -    int nn = 0;
 -    p->flgProgress = 0;
 -    p->mxProgress = 0;
 -    p->nProgress = 0;
 -    for(i=1; i<nArg; i++){
 -      const char *z = azArg[i];
 -      if( z[0]=='-' ){
 -        z++;
 -        if( z[0]=='-' ) z++;
 -        if( cli_strcmp(z,"quiet")==0 || cli_strcmp(z,"q")==0 ){
 -          p->flgProgress |= SHELL_PROGRESS_QUIET;
 -          continue;
 -        }
 -        if( cli_strcmp(z,"reset")==0 ){
 -          p->flgProgress |= SHELL_PROGRESS_RESET;
 -          continue;
 -        }
 -        if( cli_strcmp(z,"once")==0 ){
 -          p->flgProgress |= SHELL_PROGRESS_ONCE;
 -          continue;
 +    if( rc ){
 +      *pzErr = smprintf("Error querying the selftest table\n");
 +      sqlite3_finalize(pStmt);
 +      return DCR_Error;
 +    }
 +    for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){
 +      int tno = sqlite3_column_int(pStmt, 0);
 +      const char *zOp = (const char*)sqlite3_column_text(pStmt, 1);
 +      const char *zSql = (const char*)sqlite3_column_text(pStmt, 2);
 +      const char *zAns = (const char*)sqlite3_column_text(pStmt, 3);
 +
 +      if( zOp==0 || zSql==0 || zAns==0 ) continue;
 +      k = 0;
 +      if( bVerbose>0 ){
 +        /* This unusually directed output is for test purposes. */
 +        fprintf(STD_OUT, "%d: %s %s\n", tno, zOp, zSql);
 +      }
 +      if( cli_strcmp(zOp,"memo")==0 ){
 +        utf8_printf(psi->out, "%s\n", zSql);
 +      }else if( cli_strcmp(zOp,"run")==0 ){
 +        char *zErrMsg = 0;
 +        str.n = 0;
 +        str.z[0] = 0;
 +        rc = sqlite3_exec(DBX(p), zSql, captureOutputCallback, &str, &zErrMsg);
 +        nTest++;
 +        if( bVerbose ){
 +          utf8_printf(psi->out, "Result: %s\n", str.z);
          }
 -        if( cli_strcmp(z,"limit")==0 ){
 -          if( i+1>=nArg ){
 -            utf8_printf(stderr, "Error: missing argument on --limit\n");
 -            rc = 1;
 -            goto meta_command_exit;
 -          }else{
 -            p->mxProgress = (int)integerValue(azArg[++i]);
 -          }
 -          continue;
 +        if( rc || zErrMsg ){
 +          nErr++;
 +          rc = 1;
 +          utf8_printf(psi->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
 +          sqlite3_free(zErrMsg);
 +        }else if( cli_strcmp(zAns,str.z)!=0 ){
 +          nErr++;
 +          rc = 1;
 +          utf8_printf(psi->out, "%d: Expected: [%s]\n", tno, zAns);
 +          utf8_printf(psi->out, "%d:      Got: [%s]\n", tno, str.z);
          }
 -        utf8_printf(stderr, "Error: unknown option: \"%s\"\n", azArg[i]);
 -        rc = 1;
 -        goto meta_command_exit;
        }else{
 -        nn = (int)integerValue(z);
 +        *pzErr = smprintf
 +          ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
 +        rc = 1;
 +        break;
        }
 -    }
 -    open_db(p, 0);
 -    sqlite3_progress_handler(p->db, nn, progress_handler, p);
 -  }else
 -#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
 +    } /* End loop over rows of content from SELFTEST */
 +    sqlite3_finalize(pStmt);
 +  } /* End loop over k */
 +  freeText(&str);
 +  utf8_printf(psi->out, "%d errors out of %d tests\n", nErr, nTest);
 +  return rc > 0;
 +}
 +
 +/*****************
 + * The .shell and .system commands
 + */
 +CONDITION_COMMAND( shell !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) );
 +CONDITION_COMMAND( system !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) );
 +COLLECT_HELP_TEXT[
 +  ".shell CMD ARGS...       Run CMD ARGS... in a system shell",
 +  ".system CMD ARGS...      Run CMD ARGS... in a system shell",
 +];
 +
 +#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 +static DotCmdRC shellOut(char *azArg[], int nArg,
 +                         ShellExState *psx, char **pzErr){
 +  char *zCmd;
 +  int i, x;
 +  if( ISS(psx)->bSafeMode ) return DCR_AbortError;
 +  zCmd = smprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
 +  for(i=2; i<nArg; i++){
 +    zCmd = smprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
 +                    zCmd, azArg[i]);
 +  }
 +  shell_check_oom(zCmd);
 +  x = system(zCmd);
 +  sqlite3_free(zCmd);
 +  if( x ) raw_printf(STD_ERR, "%s command returns %d\n", azArg[0], x);
 +  return DCR_Ok;
 +}
 +#endif
 +DISPATCHABLE_COMMAND( shell ? 2 0 ){
 +  return shellOut(azArg, nArg, p, pzErr);
 +}
  
 -  if( c=='p' && cli_strncmp(azArg[0], "prompt", n)==0 ){
 -    if( nArg >= 2) {
 -      shell_strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1);
 -    }
 -    if( nArg >= 3) {
 -      shell_strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1);
 +DISPATCHABLE_COMMAND( system ? 2 0 ){
 +  return shellOut(azArg, nArg, p, pzErr);
 +}
 +
 +/*****************
 + * The .shxload and .shxopts commands
 + */
 +CONDITION_COMMAND( shxload (SHELL_DYNAMIC_EXTENSION)!=0 );
 +CONDITION_COMMAND( shxopts (SHELL_EXTENSIONS)!=0 );
 +COLLECT_HELP_TEXT[
 +  ".shxload FILE ?OPTIONS?  Load a CLI shell extension library",
 +  "   The first option may name the init function to be called upon load.",
 +  "   Otherwise, its name is derived from FILE. Either way, the entry point"
 +  "   \"sqlite_NAME_init\" is called. All options after \"--\" are passed to",
 +  "   the extension's init function in the ShellExtensionLink struct.",
 +  ".shxopts ?SIGNED_OPTS?   Show or alter shell extension options",
 +  "   Run without arguments to see their self-descriptive names",
 +];
 +
 +DISPATCHABLE_COMMAND( shxload 4 2 0 ){
 +  const char *zFile = 0, *zProc = 0;
 +  int ai = 1, rc;
 +  char **pzExtArgs = 0;
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  while( ai<nArg ){
 +    const char *zA = azArg[ai++];
 +    if( cli_strcmp(zA, "--")==0 ){
 +      pzExtArgs = azArg + ai;
 +      break;
      }
 -  }else
 +    if( zFile==0 ) zFile = zA;
 +    else if( zProc==0 ) zProc = zA;
 +  }
 +  if( zFile==0 ) return DCR_Missing;
 +  if( pzExtArgs==0 ) pzExtArgs = azArg + ai;
 +  rc = load_shell_extension(p, zFile, zProc, pzErr, nArg-ai, pzExtArgs);
 +  return DCR_Ok|(rc!=SQLITE_OK);
 +}
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( c=='q' && cli_strncmp(azArg[0], "quit", n)==0 ){
 -    rc = 2;
 -  }else
 +DISPATCHABLE_COMMAND( shxopts 3 0 0 ){
 +  static struct { const char *name; u8 mask; } shopts[] = {
 +#if SHELL_DYNAMIC_COMMANDS
 +    {"dyn_cmds", 1<<SHEXT_DYNCMDS_BIT},
  #endif
 -
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( c=='r' && n>=3 && cli_strncmp(azArg[0], "read", n)==0 ){
 -    FILE *inSaved = p->in;
 -    int savedLineno = p->lineno;
 -    failIfSafeMode(p, "cannot run .read in safe mode");
 -    if( nArg!=2 ){
 -      raw_printf(stderr, "Usage: .read FILE\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    if( azArg[1][0]=='|' ){
 -#ifdef SQLITE_OMIT_POPEN
 -      raw_printf(stderr, "Error: pipes are not supported in this OS\n");
 -      rc = 1;
 -      p->out = stdout;
 -#else
 -      p->in = popen(azArg[1]+1, "r");
 -      if( p->in==0 ){
 -        utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
 -        rc = 1;
 -      }else{
 -        rc = process_input(p);
 -        pclose(p->in);
 -      }
 +#if SHELL_EXTENDED_PARSING
 +    {"parsing", 1<<SHEXT_PARSING_BIT},
  #endif
 -    }else if( (p->in = openChrSource(azArg[1]))==0 ){
 -      utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
 -      rc = 1;
 -    }else{
 -      rc = process_input(p);
 -      fclose(p->in);
 +#if SHELL_VARIABLE_EXPANSION
 +    {"dot_vars", 1<<SHEXT_VAREXP_BIT},
 +#endif
 +    {"all_opts", SHELL_ALL_EXTENSIONS}
 +  };
 +  const char *zMoan = 0, *zAbout = 0;
 +  ShellInState *psi = ISS(p);
 +  int ia, io;
 +  if( nArg>1 ){
 +    for( ia=1; ia<nArg; ++ia ){
 +      char cs = azArg[ia][0];
 +      if( cs!='+' && cs!='-' ){
 +        zMoan = "arguments must have a sign prefix.";
 +        zAbout = azArg[0];
 +        goto moan_error;
 +      }
 +      for( io=0; io<ArraySize(shopts); ++io ){
 +        if( cli_strcmp(azArg[ia]+1, shopts[io].name)==0 ){
 +          if( cs=='+' ) psi->bExtendedDotCmds |= shopts[io].mask;
 +          else psi->bExtendedDotCmds &= ~shopts[io].mask;
 +          break;
 +        }
 +      }
 +      if( io==ArraySize(shopts) ){
 +        zAbout = azArg[ia];
 +        zMoan = "is not a recognized option name";
 +        goto moan_error;
 +      }
      }
 -    p->in = inSaved;
 -    p->lineno = savedLineno;
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +  }else{
 +    raw_printf(psi->out,
 +               "     name    value  \"-shxopts set\"\n"
 +               "   --------  -----  ---------------\n");
 +    for( io=0; io<ArraySize(shopts); ++io ){
 +      unsigned m = shopts[io].mask;
 +      unsigned v = ((psi->bExtendedDotCmds & m) == m)? 1 : 0;
 +      raw_printf(psi->out,
 +                 "  %9s   %2d    \"-shxopts 0x%02X\"\n",
 +                 shopts[io].name,  v, m);
 +    }
 +  }
 +  return DCR_Ok;
 + moan_error:
 +  raw_printf(STD_ERR, "Error: %s %s\n", zAbout, zMoan);
 +  return DCR_CmdErred;
 +}
  
 -#ifndef SQLITE_SHELL_FIDDLE
 -  if( c=='r' && n>=3 && cli_strncmp(azArg[0], "restore", n)==0 ){
 -    const char *zSrcFile;
 -    const char *zDb;
 -    sqlite3 *pSrc;
 -    sqlite3_backup *pBackup;
 -    int nTimeout = 0;
 -
 -    failIfSafeMode(p, "cannot run .restore in safe mode");
 -    if( nArg==2 ){
 -      zSrcFile = azArg[1];
 -      zDb = "main";
 -    }else if( nArg==3 ){
 -      zSrcFile = azArg[2];
 -      zDb = azArg[1];
 +DISPATCHABLE_COMMAND( show ? 1 1 ){
 +  static const char *azBool[] = { "off", "on", "trigger", "full"};
 +  const char *zOut;
 +  ShellInState *psi = ISS(p);
 +  FILE *out = psi->out;
 +  int i;
 +  utf8_printf(out, "%12.12s: %s\n","echo",
 +              azBool[ShellHasFlag(p, SHFLG_Echo)]);
 +  utf8_printf(out, "%12.12s: %s\n","eqp", azBool[psi->autoEQP&3]);
 +  utf8_printf(out, "%12.12s: %s\n","explain",
 +              psi->mode==MODE_Explain
 +              ? "on" : psi->autoExplain ? "auto" : "off");
 +  utf8_printf(out,"%12.12s: %s\n","headers", azBool[psi->showHeader!=0]);
 +  zOut = modeDescr[psi->mode].zModeName;
 +  i = strlen30(zOut) - modeDescr[psi->mode].bDepluralize;
 +  if( MODE_IS_COLUMNAR(psi->mode) ){
 +    utf8_printf
 +      (out, "%12.12s: %.*s --wrap %d --wordwrap %s --%squote\n", "mode",
 +       i, zOut, psi->cmOpts.iWrap,
 +       psi->cmOpts.bWordWrap ? "on" : "off",
 +       psi->cmOpts.bQuote ? "" : "no");
 +  }else{
 +    utf8_printf(out, "%12.12s: %.*s\n","mode", i, zOut);
 +  }
 +  utf8_printf(out, "%12.12s: ", "nullvalue");
 +  output_c_string(out, psi->nullValue);
 +  raw_printf(out, "\n");
 +  utf8_printf(out,"%12.12s: %s\n","output",
 +              strlen30(psi->outfile) ? psi->outfile : "stdout");
 +  utf8_printf(out,"%12.12s: ", "colseparator");
 +  output_c_string(out, psi->colSeparator);
 +  raw_printf(out, "\n");
 +  utf8_printf(out,"%12.12s: ", "rowseparator");
 +  output_c_string(out, psi->rowSeparator);
 +  raw_printf(out, "\n");
 +  switch( psi->statsOn ){
 +  case 0:  zOut = "off";     break;
 +  default: zOut = "on";      break;
 +  case 2:  zOut = "stmt";    break;
 +  case 3:  zOut = "vmstep";  break;
 +  }
 +  utf8_printf(out, "%12.12s: %s\n","stats", zOut);
 +  utf8_printf(out, "%12.12s: ", "width");
 +  for (i=0;i<p->numWidths;i++) {
 +    raw_printf(out, "%d ", p->pSpecWidths[i]);
 +  }
 +  raw_printf(out, "\n");
 +  utf8_printf(out, "%12.12s: %s\n", "filename",
 +              psi->pAuxDb->zDbFilename ? psi->pAuxDb->zDbFilename : "");
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .stats command
 + */
 +COLLECT_HELP_TEXT[
 +  ".stats ?ARG?             Show stats or turn stats on or off",
 +  "   off                      Turn off automatic stat display",
 +  "   on                       Turn on automatic stat display",
 +  "   stmt                     Show statement stats",
 +  "   vmstep                   Show the virtual machine step count only",
 +];
 +DISPATCHABLE_COMMAND( stats ? 0 0 ){
 +  ShellInState *psi = ISS(p);
 +  if( nArg==2 ){
 +    if( cli_strcmp(azArg[1],"stmt")==0 ){
 +      psi->statsOn = 2;
 +    }else if( cli_strcmp(azArg[1],"vmstep")==0 ){
 +      psi->statsOn = 3;
      }else{
 -      raw_printf(stderr, "Usage: .restore ?DB? FILE\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +      psi->statsOn = (u8)booleanValue(azArg[1]);
      }
 -    rc = sqlite3_open(zSrcFile, &pSrc);
 -    if( rc!=SQLITE_OK ){
 -      utf8_printf(stderr, "Error: cannot open \"%s\"\n", zSrcFile);
 -      close_db(pSrc);
 -      return 1;
 +  }else if( nArg==1 ){
 +    display_stats(DBX(p), psi, 0);
 +  }else{
 +    *pzErr = smprintf("Usage: .stats ?on|off|stmt|vmstep?\n");
 +    return DCR_SayUsage;
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .tables, .views, .indices and .indexes command
 + * These are together because they share implementation or are aliases.
 + */
 +COLLECT_HELP_TEXT[
 +  ".indexes ?TABLE?         Show names of indexes",
 +  "                           If TABLE is specified, only show indexes for",
 +  "                           tables matching TABLE using the LIKE operator.",
 +];
 +static int showTableLike(char *azArg[], int nArg, ShellExState *p,
 +                         char **pzErr, char ot){
 +  int rc;
 +  sqlite3_stmt *pStmt;
 +  char **azResult;
 +  int nRow, nAlloc;
 +  int ii;
 +  ShellText s;
 +  initText(&s);
 +  open_db(p, 0);
 +  rc = sqlite3_prepare_v2(DBX(p), "PRAGMA database_list", -1, &pStmt, 0);
 +  if( rc ){
 +    sqlite3_finalize(pStmt);
 +    return shellDatabaseError(DBX(p));
 +  }
 +
 +  if( nArg>2 && ot=='i' ){
 +    /* It is an historical accident that the .indexes command shows an error
 +    ** when called with the wrong number of arguments whereas the .tables
 +    ** command does not. */
 +    *pzErr = smprintf("Usage: .indexes ?LIKE-PATTERN?\n");
 +    sqlite3_finalize(pStmt);
 +    return DCR_SayUsage;
 +  }
 +  for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){
 +    const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1);
 +    const char *zFilter = "";
 +    const char *zSystem = " AND name NOT LIKE 'sqlite_%'";
 +    if( zDbName==0 ) continue;
 +    if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0);
 +    if( sqlite3_stricmp(zDbName, "main")==0 ){
 +      appendText(&s, "SELECT name FROM ", 0);
 +    }else{
 +      appendText(&s, "SELECT ", 0);
 +      appendText(&s, zDbName, '\'');
 +      appendText(&s, "||'.'||name FROM ", 0);
 +    }
 +    appendText(&s, zDbName, '"');
 +    appendText(&s, ".sqlite_schema ", 0);
 +    switch (ot) {
 +    case 'i':
 +      zFilter = "'index'";
 +      break;
 +#ifndef LEGACY_TABLES_LISTING
 +    case 't':
 +      zFilter = "'table'";
 +      break;
 +    case 'v':
 +      zFilter = "'view'";
 +      break;
 +#endif
 +    case 's':
 +      zSystem = " AND name LIKE 'sqlite_%'";
-       /* fall thru */
++      deliberate_fall_through;
 +    case 'T':
 +      zFilter = "'table','view'";
 +      break;
 +    default:
 +      assert(0);
      }
 -    open_db(p, 0);
 -    pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main");
 -    if( pBackup==0 ){
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      close_db(pSrc);
 -      return 1;
 +    appendText(&s, " WHERE type IN(", 0);
 +    appendText(&s, zFilter, 0);
 +    appendText(&s, ") AND name LIKE ?1", 0);
 +    appendText(&s, zSystem, 0);
 +  }
 +  rc = sqlite3_finalize(pStmt);
 +  if( rc==SQLITE_OK ){
 +    appendText(&s, " ORDER BY 1", 0);
 +    rc = sqlite3_prepare_v2(DBX(p), s.z, -1, &pStmt, 0);
 +  }
 +  freeText(&s);
 +  if( rc ) return shellDatabaseError(DBX(p));
 +
 +  /* Run the SQL statement prepared by the above block. Store the results
 +  ** as an array of nul-terminated strings in azResult[].  */
 +  nRow = nAlloc = 0;
 +  azResult = 0;
 +  if( nArg>1 ){
 +    sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT);
 +  }else{
 +    sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC);
 +  }
 +  while( sqlite3_step(pStmt)==SQLITE_ROW ){
 +    if( nRow>=nAlloc ){
 +      char **azNew;
 +      int n2 = nAlloc*2 + 10;
 +      azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
 +      shell_check_oom(azNew);
 +      nAlloc = n2;
 +      azResult = azNew;
 +    }
 +    azResult[nRow] = smprintf("%s", sqlite3_column_text(pStmt, 0));
 +    shell_check_oom(azResult[nRow]);
 +    nRow++;
 +  }
 +  if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
 +    rc = shellDatabaseError(DBX(p));
 +  }
 +
 +  /* Pretty-print the contents of array azResult[] to the output */
 +  if( rc==0 && nRow>0 ){
 +    int len, maxlen = 0;
 +    int i, j;
 +    int nPrintCol, nPrintRow;
 +    for(i=0; i<nRow; i++){
 +      len = strlen30(azResult[i]);
 +      if( len>maxlen ) maxlen = len;
      }
 -    while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
 -          || rc==SQLITE_BUSY  ){
 -      if( rc==SQLITE_BUSY ){
 -        if( nTimeout++ >= 3 ) break;
 -        sqlite3_sleep(100);
 +    nPrintCol = 80/(maxlen+2);
 +    if( nPrintCol<1 ) nPrintCol = 1;
 +    nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
 +    for(i=0; i<nPrintRow; i++){
 +      for(j=i; j<nRow; j+=nPrintRow){
 +        char *zSp = j<nPrintRow ? "" : "  ";
 +        utf8_printf(ISS(p)->out, "%s%-*s", zSp, maxlen,
 +                    azResult[j] ? azResult[j]:"");
        }
 +      raw_printf(ISS(p)->out, "\n");
      }
 -    sqlite3_backup_finish(pBackup);
 -    if( rc==SQLITE_DONE ){
 -      rc = 0;
 -    }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
 -      raw_printf(stderr, "Error: source database is busy\n");
 -      rc = 1;
 -    }else{
 -      utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -      rc = 1;
 -    }
 -    close_db(pSrc);
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +  }
  
 -  if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){
 -    if( nArg==2 ){
 -      if( cli_strcmp(azArg[1], "est")==0 ){
 -        p->scanstatsOn = 2;
 -      }else{
 -        p->scanstatsOn = (u8)booleanValue(azArg[1]);
 -      }
 -      open_db(p, 0);
 -      sqlite3_db_config(
 -          p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0
 -      );
 -#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
 -      raw_printf(stderr, "Warning: .scanstats not available in this build.\n");
 +  for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
 +  sqlite3_free(azResult);
 +  return DCR_Ok;
 +}
 +
 +COLLECT_HELP_TEXT[
 +#ifndef LEGACY_TABLES_LISTING
 +  ".tables ?FLAG? ?TVLIKE?  List names of tables and/or views",
 +  "   FLAG may be -t, -v or -s to list only tables, views or system tables",
 +  "   TVLIKE may restrict the listing to names matching given LIKE pattern",
 +#else
 +  ".tables ?TABLE?          List names of tables matching LIKE pattern TABLE",
  #endif
 -    }else{
 -      raw_printf(stderr, "Usage: .scanstats on|off|est\n");
 -      rc = 1;
 +];
 +DISPATCHABLE_COMMAND( tables 2 1 3 ){
 +  char objType = 'T';
 +#ifndef LEGACY_TABLES_LISTING
 +  if( nArg>1 && azArg[1][0]=='-' && azArg[1][1]!=0 && azArg[1][2]==0 ){
 +    char c = azArg[1][1];
 +    switch (c){
 +    case 's':
 +    case 't':
 +    case 'v':
 +      objType = c;
 +      ++azArg;
 +      --nArg;
 +      break;
 +    default:
 +      return DCR_Unknown|1;
      }
 -  }else
 +  }
 +#endif
 +  return showTableLike(azArg, nArg, p, pzErr, objType);
 +}
 +DISPATCHABLE_COMMAND( indexes 3 1 2 ){
 +  return showTableLike(azArg, nArg, p, pzErr, 'i');
 +}
 +DISPATCHABLE_COMMAND( indices 3 1 2 ){
 +  return showTableLike(azArg, nArg, p, pzErr, 'i');
 +}
  
 -  if( c=='s' && cli_strncmp(azArg[0], "schema", n)==0 ){
 -    ShellText sSelect;
 -    ShellState data;
 -    char *zErrMsg = 0;
 -    const char *zDiv = "(";
 -    const char *zName = 0;
 -    int iSchema = 0;
 -    int bDebug = 0;
 -    int bNoSystemTabs = 0;
 -    int ii;
 +/*****************
 + * The .selecttrace, .treetrace and .wheretrace commands (undocumented)
 + */
 +static DotCmdRC setTrace( char *azArg[], int nArg, ShellExState *psx, int ts ){
 +  unsigned int x = nArg>1 ? (unsigned int)integerValue(azArg[1]) : ~0;
 +  sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, ts, &x);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( selecttrace 0 1 2 ){
 +  return setTrace(azArg, nArg, p, 1);
 +}
 +DISPATCHABLE_COMMAND( treetrace 0 1 2 ){
 +  return setTrace(azArg, nArg, p, 1);
 +}
 +DISPATCHABLE_COMMAND( wheretrace 0 1 2 ){
 +  return setTrace(azArg, nArg, p, 3);
 +}
  
 -    open_db(p, 0);
 -    memcpy(&data, p, sizeof(data));
 -    data.showHeader = 0;
 -    data.cMode = data.mode = MODE_Semi;
 -    initText(&sSelect);
 -    for(ii=1; ii<nArg; ii++){
 -      if( optionMatch(azArg[ii],"indent") ){
 -        data.cMode = data.mode = MODE_Pretty;
 -      }else if( optionMatch(azArg[ii],"debug") ){
 -        bDebug = 1;
 -      }else if( optionMatch(azArg[ii],"nosys") ){
 -        bNoSystemTabs = 1;
 -      }else if( azArg[ii][0]=='-' ){
 -        utf8_printf(stderr, "Unknown option: \"%s\"\n", azArg[ii]);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else if( zName==0 ){
 -        zName = azArg[ii];
 -      }else{
 -        raw_printf(stderr,
 -                   "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -    }
 -    if( zName!=0 ){
 -      int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0
 -                  || sqlite3_strlike(zName, "sqlite_schema", '\\')==0
 -                  || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0
 -                  || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0;
 -      if( isSchema ){
 -        char *new_argv[2], *new_colv[2];
 -        new_argv[0] = sqlite3_mprintf(
 -                      "CREATE TABLE %s (\n"
 -                      "  type text,\n"
 -                      "  name text,\n"
 -                      "  tbl_name text,\n"
 -                      "  rootpage integer,\n"
 -                      "  sql text\n"
 -                      ")", zName);
 -        shell_check_oom(new_argv[0]);
 -        new_argv[1] = 0;
 -        new_colv[0] = "sql";
 -        new_colv[1] = 0;
 -        callback(&data, 1, new_argv, new_colv);
 -        sqlite3_free(new_argv[0]);
 -      }
 -    }
 -    if( zDiv ){
 -      sqlite3_stmt *pStmt = 0;
 -      rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list",
 -                              -1, &pStmt, 0);
 -      if( rc ){
 -        utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
 -        sqlite3_finalize(pStmt);
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      appendText(&sSelect, "SELECT sql FROM", 0);
 -      iSchema = 0;
 -      while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -        const char *zDb = (const char*)sqlite3_column_text(pStmt, 0);
 -        char zScNum[30];
 -        sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema);
 -        appendText(&sSelect, zDiv, 0);
 -        zDiv = " UNION ALL ";
 -        appendText(&sSelect, "SELECT shell_add_schema(sql,", 0);
 -        if( sqlite3_stricmp(zDb, "main")!=0 ){
 -          appendText(&sSelect, zDb, '\'');
 -        }else{
 -          appendText(&sSelect, "NULL", 0);
 -        }
 -        appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0);
 -        appendText(&sSelect, zScNum, 0);
 -        appendText(&sSelect, " AS snum, ", 0);
 -        appendText(&sSelect, zDb, '\'');
 -        appendText(&sSelect, " AS sname FROM ", 0);
 -        appendText(&sSelect, zDb, quoteChar(zDb));
 -        appendText(&sSelect, ".sqlite_schema", 0);
 -      }
 -      sqlite3_finalize(pStmt);
 -#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
 -      if( zName ){
 -        appendText(&sSelect,
 -           " UNION ALL SELECT shell_module_schema(name),"
 -           " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list",
 -        0);
 -      }
 -#endif
 -      appendText(&sSelect, ") WHERE ", 0);
 -      if( zName ){
 -        char *zQarg = sqlite3_mprintf("%Q", zName);
 -        int bGlob;
 -        shell_check_oom(zQarg);
 -        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, bGlob ? " GLOB " : " LIKE ", 0);
 -        appendText(&sSelect, zQarg, 0);
 -        if( !bGlob ){
 -          appendText(&sSelect, " ESCAPE '\\' ", 0);
 -        }
 -        appendText(&sSelect, " AND ", 0);
 -        sqlite3_free(zQarg);
 -      }
 -      if( bNoSystemTabs ){
 -        appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
 -      }
 -      appendText(&sSelect, "sql IS NOT NULL"
 -                           " ORDER BY snum, rowid", 0);
 -      if( bDebug ){
 -        utf8_printf(p->out, "SQL: %s;\n", sSelect.z);
 +/*****************
 + * The .testcase, .testctrl, .timeout, .timer and .trace commands
 + */
 +CONDITION_COMMAND( testcase !defined(SQLITE_SHELL_FIDDLE) );
 +CONDITION_COMMAND( testctrl !defined(SQLITE_UNTESTABLE) );
 +CONDITION_COMMAND( trace !defined(SQLITE_OMIT_TRACE) );
 +COLLECT_HELP_TEXT[
 +  ",testcase NAME           Begin redirecting output to 'testcase-out.txt'",
 +  ",testctrl CMD ...        Run various sqlite3_test_control() operations",
 +  "                            Run \".testctrl\" with no arguments for details",
 +  ".timeout MS              Try opening locked tables for MS milliseconds",
 +  ".timer on|off            Turn SQL timer on or off",
 +  ".trace ?OPTIONS?         Output each SQL statement as it is run",
 +  "    FILE                    Send output to FILE",
 +  "    stdout                  Send output to stdout",
 +  "    stderr                  Send output to stderr",
 +  "    off                     Disable tracing",
 +  "    --expanded              Expand query parameters",
 +#ifdef SQLITE_ENABLE_NORMALIZE
 +  "    --normalized            Normal the SQL statements",
 +#endif
 +  "    --plain                 Show SQL as it is input",
 +  "    --stmt                  Trace statement execution (SQLITE_TRACE_STMT)",
 +  "    --profile               Profile statements (SQLITE_TRACE_PROFILE)",
 +  "    --row                   Trace each row (SQLITE_TRACE_ROW)",
 +  "    --close                 Trace connection close (SQLITE_TRACE_CLOSE)",
 +];
 +
 +/* Begin redirecting output to the file "testcase-out.txt" */
 +DISPATCHABLE_COMMAND( testcase ? 0 0 ){
 +  ShellInState *psi = ISS(p);
 +  output_reset(psi);
 +  psi->out = output_file_open("testcase-out.txt", 0);
 +  if( psi->out==0 ){
 +    raw_printf(STD_ERR, "Error: cannot open 'testcase-out.txt'\n");
 +  }
 +  if( nArg>=2 ){
 +    sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "%s", azArg[1]);
 +  }else{
 +    sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "?");
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
 +  FILE *out = ISS(p)->out;
 +  static const struct {
 +    const char *zCtrlName;   /* Name of a test-control option */
 +    int ctrlCode;            /* Integer code for that option */
 +    int unSafe;              /* Not valid for --safe mode */
 +    const char *zUsage;      /* Usage notes */
 +  } aCtrl[] = {
 +    {"always",             SQLITE_TESTCTRL_ALWAYS, 1,     "BOOLEAN"         },
 +    {"assert",             SQLITE_TESTCTRL_ASSERT, 1,     "BOOLEAN"         },
 +  /*{"benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, ""        },*/
 +  /*{"bitvec_test",        SQLITE_TESTCTRL_BITVEC_TEST, 1,  ""              },*/
 +    {"byteorder",          SQLITE_TESTCTRL_BYTEORDER, 0,  ""                },
 +    {"extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN"  },
 +  /*{"fault_install",      SQLITE_TESTCTRL_FAULT_INSTALL, 1,""              },*/
 +    {"imposter",         SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"},
 +    {"internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,""          },
 +    {"localtime_fault",    SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN"      },
 +    {"never_corrupt",      SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN"       },
 +    {"optimizations",      SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK"   },
 +#ifdef YYCOVERAGE
 +    {"parser_coverage",    SQLITE_TESTCTRL_PARSER_COVERAGE,0,""             },
 +#endif
 +    {"pending_byte",       SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET  "       },
 +    {"prng_restore",       SQLITE_TESTCTRL_PRNG_RESTORE,0, ""               },
 +    {"prng_save",          SQLITE_TESTCTRL_PRNG_SAVE,   0, ""               },
 +    {"prng_seed",          SQLITE_TESTCTRL_PRNG_SEED,   0, "SEED ?db?"      },
 +    {"seek_count",         SQLITE_TESTCTRL_SEEK_COUNT,  0, ""               },
 +    {"sorter_mmap",        SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX"           },
 +    {"tune",               SQLITE_TESTCTRL_TUNE,        1, "ID VALUE"       },
 +  };
 +  int testctrl = -1;
 +  int iCtrl = -1;
 +  int rc2 = 0;    /* 0: usage.  1: %d  2: %x  3: no-output */
 +  int isOk = 0;
 +  int i, n2;
 +  const char *zCmd = 0;
 +
++  if( !ShellHasFlag(p,SHFLG_TestingMode) ){
++    utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
++                "testctrl");
++    return DCR_Error;
++  }
 +  open_db(p, 0);
 +  zCmd = nArg>=2 ? azArg[1] : "help";
 +
 +  /* The argument can optionally begin with "-" or "--" */
 +  if( zCmd[0]=='-' && zCmd[1] ){
 +    zCmd++;
 +    if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +  }
 +
 +  /* --help lists all test-controls */
 +  if( cli_strcmp(zCmd,"help")==0 ){
 +    utf8_printf(out, "Available test-controls:\n");
 +    for(i=0; i<ArraySize(aCtrl); i++){
 +      utf8_printf(out, "  .testctrl %s %s\n",
 +                  aCtrl[i].zCtrlName, aCtrl[i].zUsage);
 +    }
 +    return DCR_CmdErred;
 +  }
 +
 +  /* convert testctrl text option to value. allow any unique prefix
 +  ** of the option name, or a numerical value. */
 +  n2 = strlen30(zCmd);
 +  for(i=0; i<ArraySize(aCtrl); i++){
 +    if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 +      if( testctrl<0 ){
 +        testctrl = aCtrl[i].ctrlCode;
 +        iCtrl = i;
        }else{
 -        rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg);
 +        *pzErr = smprintf
 +          ("Error: ambiguous test-control: \"%s\"\n"
 +           "Use \".testctrl --help\" for help\n", zCmd);
 +        return DCR_ArgWrong;
        }
 -      freeText(&sSelect);
 -    }
 -    if( zErrMsg ){
 -      utf8_printf(stderr,"Error: %s\n", zErrMsg);
 -      sqlite3_free(zErrMsg);
 -      rc = 1;
 -    }else if( rc != SQLITE_OK ){
 -      raw_printf(stderr,"Error: querying schema information\n");
 -      rc = 1;
 -    }else{
 -      rc = 0;
      }
 -  }else
 -
 -  if( (c=='s' && n==11 && cli_strncmp(azArg[0], "selecttrace", n)==0)
 -   || (c=='t' && n==9  && cli_strncmp(azArg[0], "treetrace", n)==0)
 -  ){
 -    unsigned int x = nArg>=2? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 -    sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
 -  }else
 +  }
 +  if( testctrl<0 ){
 +    utf8_printf(STD_ERR,"Error: unknown test-control: %s\n"
 +                "Use \".testctrl --help\" for help\n", zCmd);
 +  }else if( aCtrl[iCtrl].unSafe && ISS(p)->bSafeMode ){
 +    utf8_printf(STD_ERR,
 +                "line %d: \".testctrl %s\" may not be used in safe mode\n",
 +                ISS(p)->pInSource->lineno, aCtrl[iCtrl].zCtrlName);
 +    exit(1);
 +  }else{
 +    switch(testctrl){
  
 -#if defined(SQLITE_ENABLE_SESSION)
 -  if( c=='s' && cli_strncmp(azArg[0],"session",n)==0 && n>=3 ){
 -    struct AuxDb *pAuxDb = p->pAuxDb;
 -    OpenSession *pSession = &pAuxDb->aSession[0];
 -    char **azCmd = &azArg[1];
 -    int iSes = 0;
 -    int nCmd = nArg - 1;
 -    int i;
 -    if( nArg<=1 ) goto session_syntax_error;
 -    open_db(p, 0);
 -    if( nArg>=3 ){
 -      for(iSes=0; iSes<pAuxDb->nSession; iSes++){
 -        if( cli_strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
 +      /* sqlite3_test_control(int, db, int) */
 +    case SQLITE_TESTCTRL_OPTIMIZATIONS:
 +      if( nArg==3 ){
 +        unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0);
 +        rc2 = sqlite3_test_control(testctrl, DBX(p), opt);
 +        isOk = 3;
        }
 -      if( iSes<pAuxDb->nSession ){
 -        pSession = &pAuxDb->aSession[iSes];
 -        azCmd++;
 -        nCmd--;
 -      }else{
 -        pSession = &pAuxDb->aSession[0];
 -        iSes = 0;
 +      break;
 +
 +      /* sqlite3_test_control(int) */
 +    case SQLITE_TESTCTRL_PRNG_SAVE:
 +    case SQLITE_TESTCTRL_PRNG_RESTORE:
 +    case SQLITE_TESTCTRL_BYTEORDER:
 +      if( nArg==2 ){
 +        rc2 = sqlite3_test_control(testctrl);
 +        isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3;
        }
 -    }
 +      break;
  
 -    /* .session attach TABLE
 -    ** Invoke the sqlite3session_attach() interface to attach a particular
 -    ** table so that it is never filtered.
 -    */
 -    if( cli_strcmp(azCmd[0],"attach")==0 ){
 -      if( nCmd!=2 ) goto session_syntax_error;
 -      if( pSession->p==0 ){
 -        session_not_open:
 -        raw_printf(stderr, "ERROR: No sessions are open\n");
 -      }else{
 -        rc = sqlite3session_attach(pSession->p, azCmd[1]);
 -        if( rc ){
 -          raw_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc);
 -          rc = 0;
 -        }
 +      /* sqlite3_test_control(int, uint) */
 +    case SQLITE_TESTCTRL_PENDING_BYTE:
 +      if( nArg==3 ){
 +        unsigned int opt = (unsigned int)integerValue(azArg[2]);
 +        rc2 = sqlite3_test_control(testctrl, opt);
 +        isOk = 3;
        }
 -    }else
 +      break;
  
 -    /* .session changeset FILE
 -    ** .session patchset FILE
 -    ** Write a changeset or patchset into a file.  The file is overwritten.
 -    */
 -    if( cli_strcmp(azCmd[0],"changeset")==0
 -     || cli_strcmp(azCmd[0],"patchset")==0
 -    ){
 -      FILE *out = 0;
 -      failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]);
 -      if( nCmd!=2 ) goto session_syntax_error;
 -      if( pSession->p==0 ) goto session_not_open;
 -      out = fopen(azCmd[1], "wb");
 -      if( out==0 ){
 -        utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n",
 -                    azCmd[1]);
 -      }else{
 -        int szChng;
 -        void *pChng;
 -        if( azCmd[0][0]=='c' ){
 -          rc = sqlite3session_changeset(pSession->p, &szChng, &pChng);
 -        }else{
 -          rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
 -        }
 -        if( rc ){
 -          printf("Error: error code %d\n", rc);
 -          rc = 0;
 +      /* sqlite3_test_control(int, int, sqlite3*) */
 +    case SQLITE_TESTCTRL_PRNG_SEED:
 +      if( nArg==3 || nArg==4 ){
 +        int ii = (int)integerValue(azArg[2]);
 +        sqlite3 *db;
 +        if( ii==0 && cli_strcmp(azArg[2],"random")==0 ){
 +          sqlite3_randomness(sizeof(ii),&ii);
 +          fprintf(STD_OUT, "-- random seed: %d\n", ii);
          }
 -        if( pChng
 -          && fwrite(pChng, szChng, 1, out)!=1 ){
 -          raw_printf(stderr, "ERROR: Failed to write entire %d-byte output\n",
 -                  szChng);
 +        if( nArg==3 ){
 +          db = 0;
 +        }else{
 +          db = DBX(p);
 +          /* Make sure the schema has been loaded */
 +          sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
          }
 -        sqlite3_free(pChng);
 -        fclose(out);
 +        rc2 = sqlite3_test_control(testctrl, ii, db);
 +        isOk = 3;
        }
 -    }else
 +      break;
  
 -    /* .session close
 -    ** Close the identified session
 -    */
 -    if( cli_strcmp(azCmd[0], "close")==0 ){
 -      if( nCmd!=1 ) goto session_syntax_error;
 -      if( pAuxDb->nSession ){
 -        session_close(pSession);
 -        pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession];
 +      /* sqlite3_test_control(int, int) */
 +    case SQLITE_TESTCTRL_ASSERT:
 +    case SQLITE_TESTCTRL_ALWAYS:
 +      if( nArg==3 ){
 +        int opt = booleanValue(azArg[2]);
 +        rc2 = sqlite3_test_control(testctrl, opt);
 +        isOk = 1;
        }
 -    }else
 +      break;
  
 -    /* .session enable ?BOOLEAN?
 -    ** Query or set the enable flag
 -    */
 -    if( cli_strcmp(azCmd[0], "enable")==0 ){
 -      int ii;
 -      if( nCmd>2 ) goto session_syntax_error;
 -      ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
 -      if( pAuxDb->nSession ){
 -        ii = sqlite3session_enable(pSession->p, ii);
 -        utf8_printf(p->out, "session %s enable flag = %d\n",
 -                    pSession->zName, ii);
 +      /* sqlite3_test_control(int, int) */
 +    case SQLITE_TESTCTRL_LOCALTIME_FAULT:
 +    case SQLITE_TESTCTRL_NEVER_CORRUPT:
 +      if( nArg==3 ){
 +        int opt = booleanValue(azArg[2]);
 +        rc2 = sqlite3_test_control(testctrl, opt);
 +        isOk = 3;
        }
 -    }else
 +      break;
  
 -    /* .session filter GLOB ....
 -    ** Set a list of GLOB patterns of table names to be excluded.
 -    */
 -    if( cli_strcmp(azCmd[0], "filter")==0 ){
 -      int ii, nByte;
 -      if( nCmd<2 ) goto session_syntax_error;
 -      if( pAuxDb->nSession ){
 -        for(ii=0; ii<pSession->nFilter; ii++){
 -          sqlite3_free(pSession->azFilter[ii]);
 -        }
 -        sqlite3_free(pSession->azFilter);
 -        nByte = sizeof(pSession->azFilter[0])*(nCmd-1);
 -        pSession->azFilter = sqlite3_malloc( nByte );
 -        if( pSession->azFilter==0 ){
 -          raw_printf(stderr, "Error: out or memory\n");
 -          exit(1);
 -        }
 -        for(ii=1; ii<nCmd; ii++){
 -          char *x = pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]);
 -          shell_check_oom(x);
 -        }
 -        pSession->nFilter = ii-1;
 -      }
 -    }else
 +      /* sqlite3_test_control(sqlite3*) */
 +    case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
 +      rc2 = sqlite3_test_control(testctrl, DBX(p));
 +      isOk = 3;
 +      break;
  
 -    /* .session indirect ?BOOLEAN?
 -    ** Query or set the indirect flag
 -    */
 -    if( cli_strcmp(azCmd[0], "indirect")==0 ){
 -      int ii;
 -      if( nCmd>2 ) goto session_syntax_error;
 -      ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
 -      if( pAuxDb->nSession ){
 -        ii = sqlite3session_indirect(pSession->p, ii);
 -        utf8_printf(p->out, "session %s indirect flag = %d\n",
 -                    pSession->zName, ii);
 +    case SQLITE_TESTCTRL_IMPOSTER:
 +      if( nArg==5 ){
 +        rc2 = sqlite3_test_control(testctrl, DBX(p),
 +                                   azArg[2],
 +                                   integerValue(azArg[3]),
 +                                   integerValue(azArg[4]));
 +        isOk = 3;
        }
 -    }else
 +      break;
  
 -    /* .session isempty
 -    ** Determine if the session is empty
 -    */
 -    if( cli_strcmp(azCmd[0], "isempty")==0 ){
 -      int ii;
 -      if( nCmd!=1 ) goto session_syntax_error;
 -      if( pAuxDb->nSession ){
 -        ii = sqlite3session_isempty(pSession->p);
 -        utf8_printf(p->out, "session %s isempty flag = %d\n",
 -                    pSession->zName, ii);
 -      }
 -    }else
 +    case SQLITE_TESTCTRL_SEEK_COUNT: {
 +      u64 x = 0;
 +      rc2 = sqlite3_test_control(testctrl, DBX(p), &x);
 +      utf8_printf(out, "%llu\n", x);
 +      isOk = 3;
 +      break;
 +    }
  
 -    /* .session list
 -    ** List all currently open sessions
 -    */
 -    if( cli_strcmp(azCmd[0],"list")==0 ){
 -      for(i=0; i<pAuxDb->nSession; i++){
 -        utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName);
 +#ifdef YYCOVERAGE
 +    case SQLITE_TESTCTRL_PARSER_COVERAGE: {
 +      if( nArg==2 ){
 +        sqlite3_test_control(testctrl, out);
 +        isOk = 3;
        }
 -    }else
 -
 -    /* .session open DB NAME
 -    ** Open a new session called NAME on the attached database DB.
 -    ** DB is normally "main".
 -    */
 -    if( cli_strcmp(azCmd[0],"open")==0 ){
 -      char *zName;
 -      if( nCmd!=3 ) goto session_syntax_error;
 -      zName = azCmd[2];
 -      if( zName[0]==0 ) goto session_syntax_error;
 -      for(i=0; i<pAuxDb->nSession; i++){
 -        if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
 -          utf8_printf(stderr, "Session \"%s\" already exists\n", zName);
 -          goto meta_command_exit;
 +      break;
 +    }
 +#endif
 +#ifdef SQLITE_DEBUG
 +    case SQLITE_TESTCTRL_TUNE: {
 +      if( nArg==4 ){
 +        int id = (int)integerValue(azArg[2]);
 +        int val = (int)integerValue(azArg[3]);
 +        sqlite3_test_control(testctrl, id, &val);
 +        isOk = 3;
 +      }else if( nArg==3 ){
 +        int id = (int)integerValue(azArg[2]);
 +        sqlite3_test_control(testctrl, -id, &rc2);
 +        isOk = 1;
 +      }else if( nArg==2 ){
 +        int id = 1;
 +        while(1){
 +          int val = 0;
 +          rc2 = sqlite3_test_control(testctrl, -id, &val);
 +          if( rc2!=SQLITE_OK ) break;
 +          if( id>1 ) utf8_printf(out, "  ");
 +          utf8_printf(out, "%d: %d", id, val);
 +          id++;
          }
 +        if( id>1 ) utf8_printf(out, "\n");
 +        isOk = 3;
        }
 -      if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
 -        raw_printf(stderr,
 -                   "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
 -        goto meta_command_exit;
 +      break;
 +    }
 +#endif
 +    }
 +  }
 +  if( isOk==0 && iCtrl>=0 ){
 +    utf8_printf(out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 +    return DCR_CmdErred;
 +  }else if( isOk==1 ){
 +    raw_printf(out, "%d\n", rc2);
 +  }else if( isOk==2 ){
 +    raw_printf(out, "0x%08x\n", rc2);
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( timeout 4 1 2 ){
 +  open_db(p, 0);
 +  sqlite3_busy_timeout(DBX(p), nArg>=2 ? (int)integerValue(azArg[1]) : 0);
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( timer ? 2 2 ){
 +  enableTimer = booleanValue(azArg[1]);
 +  if( enableTimer && !HAS_TIMER ){
 +    raw_printf(STD_ERR, "Error: timer not available on this system.\n");
 +    enableTimer = 0;
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( trace ? 0 0 ){
 +  ShellInState *psi = ISS(p);
 +  int mType = 0;
 +  int jj;
 +  open_db(p, 0);
 +  for(jj=1; jj<nArg; jj++){
 +    const char *z = azArg[jj];
 +    if( z[0]=='-' ){
 +      if( optionMatch(z, "expanded") ){
 +        psi->eTraceType = SHELL_TRACE_EXPANDED;
        }
 -      pSession = &pAuxDb->aSession[pAuxDb->nSession];
 -      rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
 -      if( rc ){
 -        raw_printf(stderr, "Cannot open session: error code=%d\n", rc);
 -        rc = 0;
 -        goto meta_command_exit;
 +#ifdef SQLITE_ENABLE_NORMALIZE
 +      else if( optionMatch(z, "normalized") ){
 +        psi->eTraceType = SHELL_TRACE_NORMALIZED;
        }
 -      pSession->nFilter = 0;
 -      sqlite3session_table_filter(pSession->p, session_filter, pSession);
 -      pAuxDb->nSession++;
 -      pSession->zName = sqlite3_mprintf("%s", zName);
 -      shell_check_oom(pSession->zName);
 -    }else
 -    /* If no command name matches, show a syntax error */
 -    session_syntax_error:
 -    showHelp(p->out, "session");
 -  }else
  #endif
 -
 -#ifdef SQLITE_DEBUG
 -  /* Undocumented commands for internal testing.  Subject to change
 -  ** without notice. */
 -  if( c=='s' && n>=10 && cli_strncmp(azArg[0], "selftest-", 9)==0 ){
 -    if( cli_strncmp(azArg[0]+9, "boolean", n-9)==0 ){
 -      int i, v;
 -      for(i=1; i<nArg; i++){
 -        v = booleanValue(azArg[i]);
 -        utf8_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v);
 +      else if( optionMatch(z, "plain") ){
 +        psi->eTraceType = SHELL_TRACE_PLAIN;
        }
 -    }
 -    if( cli_strncmp(azArg[0]+9, "integer", n-9)==0 ){
 -      int i; sqlite3_int64 v;
 -      for(i=1; i<nArg; i++){
 -        char zBuf[200];
 -        v = integerValue(azArg[i]);
 -        sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
 -        utf8_printf(p->out, "%s", zBuf);
 +      else if( optionMatch(z, "profile") ){
 +        mType |= SQLITE_TRACE_PROFILE;
 +      }
 +      else if( optionMatch(z, "row") ){
 +        mType |= SQLITE_TRACE_ROW;
 +      }
 +      else if( optionMatch(z, "stmt") ){
 +        mType |= SQLITE_TRACE_STMT;
        }
 +      else if( optionMatch(z, "close") ){
 +        mType |= SQLITE_TRACE_CLOSE;
 +      }
 +      else {
 +        return DCR_Unknown|jj;
 +      }
 +    }else{
 +      output_file_close(psi->traceOut);
 +      psi->traceOut = output_file_open(z, 0);
      }
 -  }else
 -#endif
 +  }
 +  if( psi->traceOut==0 ){
 +    sqlite3_trace_v2(DBX(p), 0, 0, 0);
 +  }else{
 +    if( mType==0 ) mType = SQLITE_TRACE_STMT;
 +    sqlite3_trace_v2(DBX(p), mType, sql_trace_callback, psi);
 +  }
 +  return DCR_Ok;
 +}
  
 -  if( c=='s' && n>=4 && cli_strncmp(azArg[0],"selftest",n)==0 ){
 -    int bIsInit = 0;         /* True to initialize the SELFTEST table */
 -    int bVerbose = 0;        /* Verbose output */
 -    int bSelftestExists;     /* True if SELFTEST already exists */
 -    int i, k;                /* Loop counters */
 -    int nTest = 0;           /* Number of tests runs */
 -    int nErr = 0;            /* Number of errors seen */
 -    ShellText str;           /* Answer for a query */
 -    sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */
 +/*****************
 + * The .unknown command (undocumented)
 + */
 +COLLECT_HELP_TEXT[
 +  ",unknown ?ARGS?          Handle attempt to use an unknown dot command",
 +  "   The invocation dispatcher calls this after replacing azArg[0] with the",
 +  "   mystery command name, leaving remaining arguments as originally passed.",
 +  "   An extension may override this to provide new dot commands dynamically.",
 +  "   This name and operation were inspired by a similar feature of TCL.",
 +];
 +DISPATCHABLE_COMMAND( unknown ? 1 0 ){
 +  /* Dispatcher will call this for dot commands it cannot find. */
 +  return DCR_Unknown|0;
 +}
  
 -    open_db(p,0);
 -    for(i=1; i<nArg; i++){
 -      const char *z = azArg[i];
 -      if( z[0]=='-' && z[1]=='-' ) z++;
 -      if( cli_strcmp(z,"-init")==0 ){
 -        bIsInit = 1;
 -      }else
 -      if( cli_strcmp(z,"-v")==0 ){
 -        bVerbose++;
 -      }else
 -      {
 -        utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
 -                    azArg[i], azArg[0]);
 -        raw_printf(stderr, "Should be one of: --init -v\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 +/*****************
 + * The .unmodule command
 + */
 +CONDITION_COMMAND( unmodule defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE) );
 +COLLECT_HELP_TEXT[
 +  ".unmodule NAME ...       Unregister virtual table modules",
 +  "    --allexcept             Unregister everything except those named",
 +];
 +DISPATCHABLE_COMMAND( unmodule ? 2 0 ){
 +  int ii;
 +  int lenOpt;
 +  char *zOpt;
 +  open_db(p, 0);
 +  zOpt = azArg[1];
 +  if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++;
 +  lenOpt = (int)strlen(zOpt);
 +  if( lenOpt>=3 && cli_strncmp(zOpt, "-allexcept",lenOpt)==0 ){
 +    assert( azArg[nArg]==0 );
 +    sqlite3_drop_modules(DBX(p), nArg>2 ? (const char**)(azArg+2) : 0);
 +  }else{
 +    for(ii=1; ii<nArg; ii++){
 +      sqlite3_create_module(DBX(p), azArg[ii], 0, 0);
      }
 -    if( sqlite3_table_column_metadata(p->db,"main","selftest",0,0,0,0,0,0)
 -           != SQLITE_OK ){
 -      bSelftestExists = 0;
 -    }else{
 -      bSelftestExists = 1;
 -    }
 -    if( bIsInit ){
 -      createSelftestTable(p);
 -      bSelftestExists = 1;
 -    }
 -    initText(&str);
 -    appendText(&str, "x", 0);
 -    for(k=bSelftestExists; k>=0; k--){
 -      if( k==1 ){
 -        rc = sqlite3_prepare_v2(p->db,
 -            "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
 -            -1, &pStmt, 0);
 -      }else{
 -        rc = sqlite3_prepare_v2(p->db,
 -          "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
 -          "      (1,'run','PRAGMA integrity_check','ok')",
 -          -1, &pStmt, 0);
 -      }
 -      if( rc ){
 -        raw_printf(stderr, "Error querying the selftest table\n");
 -        rc = 1;
 -        sqlite3_finalize(pStmt);
 -        goto meta_command_exit;
 -      }
 -      for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){
 -        int tno = sqlite3_column_int(pStmt, 0);
 -        const char *zOp = (const char*)sqlite3_column_text(pStmt, 1);
 -        const char *zSql = (const char*)sqlite3_column_text(pStmt, 2);
 -        const char *zAns = (const char*)sqlite3_column_text(pStmt, 3);
 -
 -        if( zOp==0 ) continue;
 -        if( zSql==0 ) continue;
 -        if( zAns==0 ) continue;
 -        k = 0;
 -        if( bVerbose>0 ){
 -          printf("%d: %s %s\n", tno, zOp, zSql);
 -        }
 -        if( cli_strcmp(zOp,"memo")==0 ){
 -          utf8_printf(p->out, "%s\n", zSql);
 -        }else
 -        if( cli_strcmp(zOp,"run")==0 ){
 -          char *zErrMsg = 0;
 -          str.n = 0;
 -          str.z[0] = 0;
 -          rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg);
 -          nTest++;
 -          if( bVerbose ){
 -            utf8_printf(p->out, "Result: %s\n", str.z);
 -          }
 -          if( rc || zErrMsg ){
 -            nErr++;
 -            rc = 1;
 -            utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
 -            sqlite3_free(zErrMsg);
 -          }else if( cli_strcmp(zAns,str.z)!=0 ){
 -            nErr++;
 -            rc = 1;
 -            utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns);
 -            utf8_printf(p->out, "%d:      Got: [%s]\n", tno, str.z);
 -          }
 -        }else
 -        {
 -          utf8_printf(stderr,
 -            "Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
 -          rc = 1;
 -          break;
 -        }
 -      } /* End loop over rows of content from SELFTEST */
 -      sqlite3_finalize(pStmt);
 -    } /* End loop over k */
 -    freeText(&str);
 -    utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest);
 -  }else
 +  }
 +  return DCR_Ok;
 +}
  
 -  if( c=='s' && cli_strncmp(azArg[0], "separator", n)==0 ){
 -    if( nArg<2 || nArg>3 ){
 -      raw_printf(stderr, "Usage: .separator COL ?ROW?\n");
 -      rc = 1;
 -    }
 -    if( nArg>=2 ){
 -      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator,
 -                       "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]);
 -    }
 -    if( nArg>=3 ){
 -      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator,
 -                       "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]);
 +/*****************
 + * The .user command
 + * Because there is no help text for .user, it does its own argument validation.
 + */
 +CONDITION_COMMAND( user SQLITE_USER_AUTHENTICATION );
 +DISPATCHABLE_COMMAND( user ? 0 0 ){
 +  int rc;
 +  const char *usage
 +    = "Usage: .user SUBCOMMAND ...\n"
 +      "Subcommands are:\n"
 +      "   login USER PASSWORD\n"
 +      "   delete USER\n"
 +      "   add USER PASSWORD ISADMIN\n"
 +      "   edit USER PASSWORD ISADMIN\n"
 +    ;
 +  if( nArg<2 ){
 +  teach_fail:
 +    *pzErr = smprintf(usage);
 +    return DCR_SayUsage;
 +  }
 +  open_db(p, 0);
 +  if( cli_strcmp(azArg[1],"login")==0 ){
 +    if( nArg!=4 ){
 +      goto teach_fail;
      }
 -  }else
 -
 -  if( c=='s' && n>=4 && cli_strncmp(azArg[0],"sha3sum",n)==0 ){
 -    const char *zLike = 0;   /* Which table to checksum. 0 means everything */
 -    int i;                   /* Loop counter */
 -    int bSchema = 0;         /* Also hash the schema */
 -    int bSeparate = 0;       /* Hash each table separately */
 -    int iSize = 224;         /* Hash algorithm to use */
 -    int bDebug = 0;          /* Only show the query that would have run */
 -    sqlite3_stmt *pStmt;     /* For querying tables names */
 -    char *zSql;              /* SQL to be run */
 -    char *zSep;              /* Separator */
 -    ShellText sSql;          /* Complete SQL for the query to run the hash */
 -    ShellText sQuery;        /* Set of queries used to read all content */
 -    open_db(p, 0);
 -    for(i=1; i<nArg; i++){
 -      const char *z = azArg[i];
 -      if( z[0]=='-' ){
 -        z++;
 -        if( z[0]=='-' ) z++;
 -        if( cli_strcmp(z,"schema")==0 ){
 -          bSchema = 1;
 -        }else
 -        if( cli_strcmp(z,"sha3-224")==0 || cli_strcmp(z,"sha3-256")==0
 -         || cli_strcmp(z,"sha3-384")==0 || cli_strcmp(z,"sha3-512")==0
 -        ){
 -          iSize = atoi(&z[5]);
 -        }else
 -        if( cli_strcmp(z,"debug")==0 ){
 -          bDebug = 1;
 -        }else
 -        {
 -          utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
 -                      azArg[i], azArg[0]);
 -          showHelp(p->out, azArg[0]);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 -      }else if( zLike ){
 -        raw_printf(stderr, "Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }else{
 -        zLike = z;
 -        bSeparate = 1;
 -        if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
 -      }
 +    rc = sqlite3_user_authenticate(DBX(p), azArg[2], azArg[3],
 +                                   strlen30(azArg[3]));
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"Authentication failed for user %s\n", azArg[2]);
 +      return DCR_Error;
      }
 -    if( bSchema ){
 -      zSql = "SELECT lower(name) as tname FROM sqlite_schema"
 -             " WHERE type='table' AND coalesce(rootpage,0)>1"
 -             " UNION ALL SELECT 'sqlite_schema'"
 -             " ORDER BY 1 collate nocase";
 -    }else{
 -      zSql = "SELECT lower(name) as tname FROM sqlite_schema"
 -             " WHERE type='table' AND coalesce(rootpage,0)>1"
 -             " AND name NOT LIKE 'sqlite_%'"
 -             " ORDER BY 1 collate nocase";
 -    }
 -    sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 -    initText(&sQuery);
 -    initText(&sSql);
 -    appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
 -    zSep = "VALUES(";
 -    while( SQLITE_ROW==sqlite3_step(pStmt) ){
 -      const char *zTab = (const char*)sqlite3_column_text(pStmt,0);
 -      if( zTab==0 ) continue;
 -      if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue;
 -      if( cli_strncmp(zTab, "sqlite_",7)!=0 ){
 -        appendText(&sQuery,"SELECT * FROM ", 0);
 -        appendText(&sQuery,zTab,'"');
 -        appendText(&sQuery," NOT INDEXED;", 0);
 -      }else if( cli_strcmp(zTab, "sqlite_schema")==0 ){
 -        appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
 -                           " ORDER BY name;", 0);
 -      }else if( cli_strcmp(zTab, "sqlite_sequence")==0 ){
 -        appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
 -                           " ORDER BY name;", 0);
 -      }else if( cli_strcmp(zTab, "sqlite_stat1")==0 ){
 -        appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
 -                           " ORDER BY tbl,idx;", 0);
 -      }else if( cli_strcmp(zTab, "sqlite_stat4")==0 ){
 -        appendText(&sQuery, "SELECT * FROM ", 0);
 -        appendText(&sQuery, zTab, 0);
 -        appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0);
 -      }
 -      appendText(&sSql, zSep, 0);
 -      appendText(&sSql, sQuery.z, '\'');
 -      sQuery.n = 0;
 -      appendText(&sSql, ",", 0);
 -      appendText(&sSql, zTab, '\'');
 -      zSep = "),(";
 +  }else if( cli_strcmp(azArg[1],"add")==0 ){
 +    if( nArg!=5 ){
 +      goto teach_fail;
      }
 -    sqlite3_finalize(pStmt);
 -    if( bSeparate ){
 -      zSql = sqlite3_mprintf(
 -          "%s))"
 -          " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label"
 -          "   FROM [sha3sum$query]",
 -          sSql.z, iSize);
 -    }else{
 -      zSql = sqlite3_mprintf(
 -          "%s))"
 -          " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash"
 -          "   FROM [sha3sum$query]",
 -          sSql.z, iSize);
 +    rc = sqlite3_user_add(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
 +                          booleanValue(azArg[4]));
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"User-Add failed: %d\n", rc);
 +      return DCR_Error;
      }
 -    shell_check_oom(zSql);
 -    freeText(&sQuery);
 -    freeText(&sSql);
 -    if( bDebug ){
 -      utf8_printf(p->out, "%s\n", zSql);
 -    }else{
 -      shell_exec(p, zSql, 0);
 +  }else if( cli_strcmp(azArg[1],"edit")==0 ){
 +    if( nArg!=5 ){
 +      goto teach_fail;
      }
 -#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && !defined(SQLITE_OMIT_VIRTUALTABLE)
 -    {
 -      int lrc;
 -      char *zRevText = /* Query for reversible to-blob-to-text check */
 -        "SELECT lower(name) as tname FROM sqlite_schema\n"
 -        "WHERE type='table' AND coalesce(rootpage,0)>1\n"
 -        "AND name NOT LIKE 'sqlite_%%'%s\n"
 -        "ORDER BY 1 collate nocase";
 -      zRevText = sqlite3_mprintf(zRevText, zLike? " AND name LIKE $tspec" : "");
 -      zRevText = sqlite3_mprintf(
 -          /* lower-case query is first run, producing upper-case query. */
 -          "with tabcols as materialized(\n"
 -          "select tname, cname\n"
 -          "from ("
 -          " select ss.tname as tname, ti.name as cname\n"
 -          " from (%z) ss\n inner join pragma_table_info(tname) ti))\n"
 -          "select 'SELECT total(bad_text_count) AS bad_text_count\n"
 -          "FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n"
 -          " from (select 'SELECT COUNT(*) AS bad_text_count\n"
 -          "FROM '||tname||' WHERE '\n"
 -          "||group_concat('CAST(CAST('||cname||' AS BLOB) AS TEXT)<>'||cname\n"
 -          "|| ' AND typeof('||cname||')=''text'' ',\n"
 -          "' OR ') as query, tname from tabcols group by tname)"
 -          , zRevText);
 -      shell_check_oom(zRevText);
 -      if( bDebug ) utf8_printf(p->out, "%s\n", zRevText);
 -      lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0);
 -      if( lrc!=SQLITE_OK ){
 -        /* assert(lrc==SQLITE_NOMEM); // might also be SQLITE_ERROR if the
 -        ** user does cruel and unnatural things like ".limit expr_depth 0". */
 -        rc = 1;
 -      }else{
 -        if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC);
 -        lrc = SQLITE_ROW==sqlite3_step(pStmt);
 -        if( lrc ){
 -          const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0);
 -          sqlite3_stmt *pCheckStmt;
 -          lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0);
 -          if( bDebug ) utf8_printf(p->out, "%s\n", zGenQuery);
 -          if( lrc!=SQLITE_OK ){
 -            rc = 1;
 -          }else{
 -            if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){
 -              double countIrreversible = sqlite3_column_double(pCheckStmt, 0);
 -              if( countIrreversible>0 ){
 -                int sz = (int)(countIrreversible + 0.5);
 -                utf8_printf(stderr,
 -                     "Digest includes %d invalidly encoded text field%s.\n",
 -                            sz, (sz>1)? "s": "");
 -              }
 -            }
 -            sqlite3_finalize(pCheckStmt);
 -          }
 -          sqlite3_finalize(pStmt);
 -        }
 -      }
 -      if( rc ) utf8_printf(stderr, ".sha3sum failed.\n");
 -      sqlite3_free(zRevText);
 +    rc = sqlite3_user_change(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
 +                             booleanValue(azArg[4]));
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"User-Edit failed: %d\n", rc);
 +      return DCR_Error;
      }
 -#endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */
 -    sqlite3_free(zSql);
 -  }else
 -
 -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 -  if( c=='s'
 -   && (cli_strncmp(azArg[0], "shell", n)==0
 -       || cli_strncmp(azArg[0],"system",n)==0)
 -  ){
 -    char *zCmd;
 -    int i, x;
 -    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 -    if( nArg<2 ){
 -      raw_printf(stderr, "Usage: .system COMMAND\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +  }else if( cli_strcmp(azArg[1],"delete")==0 ){
 +    if( nArg!=3 ){
 +      goto teach_fail;
      }
 -    zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
 -    for(i=2; i<nArg && zCmd!=0; i++){
 -      zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
 -                             zCmd, azArg[i]);
 +    rc = sqlite3_user_delete(DBX(p), azArg[2]);
 +    if( rc ){
 +      *pzErr = shellMPrintf(0,"User-Delete failed: %d\n", rc);
 +      return DCR_Error;
      }
 -    x = zCmd!=0 ? system(zCmd) : 1;
 -    sqlite3_free(zCmd);
 -    if( x ) raw_printf(stderr, "System command returns %d\n", x);
 -  }else
 -#endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */
 +  }else{
 +    goto teach_fail;
 +  }
 +  return DCR_Ok;
 +}
  
 -  if( c=='s' && cli_strncmp(azArg[0], "show", n)==0 ){
 -    static const char *azBool[] = { "off", "on", "trigger", "full"};
 -    const char *zOut;
 -    int i;
 -    if( nArg!=1 ){
 -      raw_printf(stderr, "Usage: .show\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    utf8_printf(p->out, "%12.12s: %s\n","echo",
 -                azBool[ShellHasFlag(p, SHFLG_Echo)]);
 -    utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]);
 -    utf8_printf(p->out, "%12.12s: %s\n","explain",
 -         p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off");
 -    utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]);
 -    if( p->mode==MODE_Column
 -     || (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
 -    ){
 -      utf8_printf
 -        (p->out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode",
 -         modeDescr[p->mode], p->cmOpts.iWrap,
 -         p->cmOpts.bWordWrap ? "on" : "off",
 -         p->cmOpts.bQuote ? "" : "no");
 -    }else{
 -      utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]);
 -    }
 -    utf8_printf(p->out, "%12.12s: ", "nullvalue");
 -      output_c_string(p->out, p->nullValue);
 -      raw_printf(p->out, "\n");
 -    utf8_printf(p->out,"%12.12s: %s\n","output",
 -            strlen30(p->outfile) ? p->outfile : "stdout");
 -    utf8_printf(p->out,"%12.12s: ", "colseparator");
 -      output_c_string(p->out, p->colSeparator);
 -      raw_printf(p->out, "\n");
 -    utf8_printf(p->out,"%12.12s: ", "rowseparator");
 -      output_c_string(p->out, p->rowSeparator);
 -      raw_printf(p->out, "\n");
 -    switch( p->statsOn ){
 -      case 0:  zOut = "off";     break;
 -      default: zOut = "on";      break;
 -      case 2:  zOut = "stmt";    break;
 -      case 3:  zOut = "vmstep";  break;
 -    }
 -    utf8_printf(p->out, "%12.12s: %s\n","stats", zOut);
 -    utf8_printf(p->out, "%12.12s: ", "width");
 -    for (i=0;i<p->nWidth;i++) {
 -      raw_printf(p->out, "%d ", p->colWidth[i]);
 -    }
 -    raw_printf(p->out, "\n");
 -    utf8_printf(p->out, "%12.12s: %s\n", "filename",
 -                p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
 -  }else
 +/*****************
 + * The .vars command
 + */
 +COLLECT_HELP_TEXT[
 +  ".vars ?OPTIONS? ...      Manipulate and display shell variables",
 +  "   clear ?NAMES?           Erase all or only given named variables",
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  "   edit ?-e? NAME          Use edit() to create or alter variable NAME",
 +  "      With a -e option, the edited value is evaluated as a SQL expression.",
 +#endif
 +  "   list ?PATTERNS?         List shell variables table values",
 +  "      Alternatively, to list just some or all names: ls ?PATTERNS?",
 +  "   load ?FILE? ?NAMES?     Load some or all named variables from FILE",
 +  "      If FILE missing, empty or '~', it defaults to ~/sqlite_vars.sdb",
 +  "   save ?FILE? ?NAMES?     Save some or all named variables into FILE",
 +  "      If FILE missing, empty or '~', it defaults to ~/sqlite_vars.sdb",
 +  "   set NAME VALUE          Give shell variable NAME a value of VALUE",
 +  "      NAME must begin with a letter to be executable by .x, Other leading",
 +  "      characters have special uses. VALUE is the space-joined arguments.",
 +  "   unset ?NAMES?           Remove named variables(s) from variables table",
 +];
 +DISPATCHABLE_COMMAND( vars 2 1 0 ){
 +  DotCmdRC rv = DCR_Ok;
 +  sqlite3 *dbs = p->dbShell;
 +  const char *zCmd = (nArg>1)? azArg[1] : "ls";
 +  int rc = 0;
 +  int ncCmd = strlen30(zCmd);
  
 -  if( c=='s' && cli_strncmp(azArg[0], "stats", n)==0 ){
 -    if( nArg==2 ){
 -      if( cli_strcmp(azArg[1],"stmt")==0 ){
 -        p->statsOn = 2;
 -      }else if( cli_strcmp(azArg[1],"vmstep")==0 ){
 -        p->statsOn = 3;
 -      }else{
 -        p->statsOn = (u8)booleanValue(azArg[1]);
 -      }
 -    }else if( nArg==1 ){
 -      display_stats(p->db, p, 0);
 -    }else{
 -      raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n");
 -      rc = 1;
 -    }
 -  }else
 +  if( *zCmd>'e' && ncCmd<2 ) return DCR_Ambiguous|1;
 +#define SUBCMD(scn) (cli_strncmp(zCmd, scn, ncCmd)==0)
  
 -  if( (c=='t' && n>1 && cli_strncmp(azArg[0], "tables", n)==0)
 -   || (c=='i' && (cli_strncmp(azArg[0], "indices", n)==0
 -                 || cli_strncmp(azArg[0], "indexes", n)==0) )
 -  ){
 -    sqlite3_stmt *pStmt;
 -    char **azResult;
 -    int nRow, nAlloc;
 -    int ii;
 -    ShellText s;
 -    initText(&s);
 -    open_db(p, 0);
 -    rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
 -    if( rc ){
 -      sqlite3_finalize(pStmt);
 -      return shellDatabaseError(p->db);
 -    }
 +  /* This could be done lazily, but with more code. */
 +  if( (dbs && ensure_shvars_table(dbs)!=SQLITE_OK) ){
 +    return DCR_Error;
 +  }else{
 +    if( ensure_shell_db(p)!=SQLITE_OK ) return DCR_Error;
 +    dbs = p->dbShell;
 +    assert(dbs!=0);
 +    if( ensure_shvars_table(dbs)!=SQLITE_OK ) return DCR_Error;
 +  }
  
 -    if( nArg>2 && c=='i' ){
 -      /* It is an historical accident that the .indexes command shows an error
 -      ** when called with the wrong number of arguments whereas the .tables
 -      ** command does not. */
 -      raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n");
 -      rc = 1;
 -      sqlite3_finalize(pStmt);
 -      goto meta_command_exit;
 -    }
 -    for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){
 -      const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1);
 -      if( zDbName==0 ) continue;
 -      if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0);
 -      if( sqlite3_stricmp(zDbName, "main")==0 ){
 -        appendText(&s, "SELECT name FROM ", 0);
 -      }else{
 -        appendText(&s, "SELECT ", 0);
 -        appendText(&s, zDbName, '\'');
 -        appendText(&s, "||'.'||name FROM ", 0);
 -      }
 -      appendText(&s, zDbName, '"');
 -      appendText(&s, ".sqlite_schema ", 0);
 -      if( c=='t' ){
 -        appendText(&s," WHERE type IN ('table','view')"
 -                      "   AND name NOT LIKE 'sqlite_%'"
 -                      "   AND name LIKE ?1", 0);
 -      }else{
 -        appendText(&s," WHERE type='index'"
 -                      "   AND tbl_name LIKE ?1", 0);
 -      }
 -    }
 -    rc = sqlite3_finalize(pStmt);
 -    if( rc==SQLITE_OK ){
 -      appendText(&s, " ORDER BY 1", 0);
 -      rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
 -    }
 -    freeText(&s);
 -    if( rc ) return shellDatabaseError(p->db);
 -
 -    /* Run the SQL statement prepared by the above block. Store the results
 -    ** as an array of nul-terminated strings in azResult[].  */
 -    nRow = nAlloc = 0;
 -    azResult = 0;
 -    if( nArg>1 ){
 -      sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT);
 -    }else{
 -      sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC);
 +  /* .vars clear  and  .vars unset ?NAMES?
 +  **  Delete some or all key/value pairs from the shell variables table.
 +  **  Without any arguments, clear deletes them all and unset does nothing.
 +  */
 +  if( SUBCMD("clear") || SUBCMD("unset") ){
 +    if( (nArg>2 || zCmd[0]=='c') ){
 +      sqlite3_str *sbZap = sqlite3_str_new(dbs);
 +      char *zSql;
 +      sqlite3_str_appendf
 +        (sbZap, "DELETE FROM "SHVAR_TABLE_SNAME" WHERE key ");
 +      append_in_clause(sbZap,
 +                       (const char **)&azArg[2], (const char **)&azArg[nArg]);
 +      zSql = sqlite3_str_finish(sbZap);
 +      shell_check_oom(zSql);
 +      rc = sqlite3_exec(dbs, zSql, 0, 0, 0);
 +      sqlite3_free(zSql);
      }
 -    while( sqlite3_step(pStmt)==SQLITE_ROW ){
 -      if( nRow>=nAlloc ){
 -        char **azNew;
 -        int n2 = nAlloc*2 + 10;
 -        azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
 -        shell_check_oom(azNew);
 -        nAlloc = n2;
 -        azResult = azNew;
 -      }
 -      azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 -      shell_check_oom(azResult[nRow]);
 -      nRow++;
 -    }
 -    if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
 -      rc = shellDatabaseError(p->db);
 -    }
 -
 -    /* Pretty-print the contents of array azResult[] to the output */
 -    if( rc==0 && nRow>0 ){
 -      int len, maxlen = 0;
 -      int i, j;
 -      int nPrintCol, nPrintRow;
 -      for(i=0; i<nRow; i++){
 -        len = strlen30(azResult[i]);
 -        if( len>maxlen ) maxlen = len;
 -      }
 -      nPrintCol = 80/(maxlen+2);
 -      if( nPrintCol<1 ) nPrintCol = 1;
 -      nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
 -      for(i=0; i<nPrintRow; i++){
 -        for(j=i; j<nRow; j+=nPrintRow){
 -          char *zSp = j<nPrintRow ? "" : "  ";
 -          utf8_printf(p->out, "%s%-*s", zSp, maxlen,
 -                      azResult[j] ? azResult[j]:"");
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  }else if( SUBCMD("edit") ){
 +    ShellInState *psi = ISS(p);
 +    int ia = 2;
 +    int eval = 0;
 +    int edSet;
 +
 +    if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
 +      utf8_printf(STD_ERR, "Error: "
 +                  ".vars edit can only be used interactively.\n");
 +      return DCR_Error;
 +    }
 +    edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 );
 +    if( edSet < 0 ) return DCR_Error;
 +    else ia += edSet;
 +    while( ia < nArg ){
 +      ParamTableUse ptu;
 +      char *zA = azArg[ia];
 +      char cf = (zA[0]=='-')? zA[1] : 0;
 +      if( cf!=0 && zA[2]==0 ){
 +        ++ia;
 +        switch( cf ){
 +        case 'e': eval = 1; continue;
 +        case 't': eval = 0; continue;
 +        default:
 +          utf8_printf(STD_ERR, "Error: bad .vars edit option: %s\n", zA);
 +          return DCR_Error;
          }
 -        raw_printf(p->out, "\n");
        }
 +      ptu = classify_param_name(zA);
 +      if( ptu!=PTU_Script ){
 +        utf8_printf(STD_ERR,
 +                    "Error: %s cannot be a shell variable name.\n", zA);
 +        return DCR_Error;
 +      }
 +      rc = edit_one_kvalue(dbs, zA, eval, ptu, psi->zEditor);
 +      ++ia;
 +      if( rc!=0 ) return DCR_Error;
      }
 -
 -    for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
 -    sqlite3_free(azResult);
 -  }else
 -
 -#ifndef SQLITE_SHELL_FIDDLE
 -  /* Begin redirecting output to the file "testcase-out.txt" */
 -  if( c=='t' && cli_strcmp(azArg[0],"testcase")==0 ){
 -    output_reset(p);
 -    p->out = output_file_open("testcase-out.txt", 0);
 -    if( p->out==0 ){
 -      raw_printf(stderr, "Error: cannot open 'testcase-out.txt'\n");
 -    }
 -    if( nArg>=2 ){
 -      sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]);
 +#endif
 +  }else if( SUBCMD("list") || SUBCMD("ls") ){
 +    int nTailArgs = nArg - 1 - (nArg>1);
 +    char **pzTailArgs = azArg + 1 + (nArg>1);
 +    list_pov_entries(p, PTU_Script, zCmd[1]=='s', pzTailArgs, nTailArgs);
 +  }else if( SUBCMD("load") ){
 +    rc = kv_pairs_load(dbs, PTU_Script, (const char **)azArg+1, nArg-1);
 +  }else if( SUBCMD("save") ){
 +    rc = kv_pairs_save(dbs, PTU_Script, (const char **)azArg+1, nArg-1);
 +  }else if( SUBCMD("set") ){
 +    ParamTableUse ptu;
 +    if( nArg<4 ) return DCR_Missing;
 +    ptu = classify_param_name(azArg[2]);
 +    if( ptu!=PTU_Script ){
 +      utf8_printf(STD_ERR,
 +                  "Error: %s is not a valid shell variable name.\n", azArg[2]);
 +      rc = 1;
      }else{
 -      sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?");
 +      rc = shvar_set(dbs, azArg[2], &azArg[3], &azArg[nArg]);
 +      if( rc!=SQLITE_OK ){
 +        utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(dbs));
 +        rc = 1;
 +      }
      }
 -  }else
 -#endif /* !defined(SQLITE_SHELL_FIDDLE) */
 +  }else{
 +    showHelp(ISS(p)->out, "vars", p);
 +    return DCR_CmdErred;
 +  }
 +  return DCR_Ok | (rv!=0) | (rc!=0);
 +#undef SUBCMD
 +}
  
 -#ifndef SQLITE_UNTESTABLE
 -  if( c=='t' && n>=8 && cli_strncmp(azArg[0], "testctrl", n)==0 ){
 -    static const struct {
 -       const char *zCtrlName;   /* Name of a test-control option */
 -       int ctrlCode;            /* Integer code for that option */
 -       int unSafe;              /* Not valid for --safe mode */
 -       const char *zUsage;      /* Usage notes */
 -    } aCtrl[] = {
 -    {"always",             SQLITE_TESTCTRL_ALWAYS, 1,     "BOOLEAN"         },
 -    {"assert",             SQLITE_TESTCTRL_ASSERT, 1,     "BOOLEAN"         },
 -  /*{"benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, ""        },*/
 -  /*{"bitvec_test",        SQLITE_TESTCTRL_BITVEC_TEST, 1,  ""              },*/
 -    {"byteorder",          SQLITE_TESTCTRL_BYTEORDER, 0,  ""                },
 -    {"extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN"  },
 -  /*{"fault_install",      SQLITE_TESTCTRL_FAULT_INSTALL, 1,""              },*/
 -    {"imposter",         SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"},
 -    {"internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,""          },
 -    {"localtime_fault",    SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN"      },
 -    {"never_corrupt",      SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN"       },
 -    {"optimizations",      SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK"   },
 -#ifdef YYCOVERAGE
 -    {"parser_coverage",    SQLITE_TESTCTRL_PARSER_COVERAGE,0,""             },
 +/*****************
 + * The .vfsinfo, .vfslist, .vfsname and .version commands
 + */
 +COLLECT_HELP_TEXT[
-   ".version                 Show a variety of version info",
++  ".version                 Show source, library and compiler versions",
 +  ".vfsinfo ?AUX?           Information about the top-level VFS",
 +  ".vfslist                 List all available VFSes",
 +  ".vfsname ?AUX?           Print the name of the VFS stack",
 +];
 +DISPATCHABLE_COMMAND( version ? 1 1 ){
 +  FILE *out = ISS(p)->out;
 +  utf8_printf(out, "SQLite %s %s\n" /*extra-version-info*/,
 +              sqlite3_libversion(), sqlite3_sourceid());
 +#if SQLITE_HAVE_ZLIB
 +  utf8_printf(out, "zlib version %s\n", zlibVersion());
  #endif
 -    {"pending_byte",       SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET  "       },
 -    {"prng_restore",       SQLITE_TESTCTRL_PRNG_RESTORE,0, ""               },
 -    {"prng_save",          SQLITE_TESTCTRL_PRNG_SAVE,   0, ""               },
 -    {"prng_seed",          SQLITE_TESTCTRL_PRNG_SEED,   0, "SEED ?db?"      },
 -    {"seek_count",         SQLITE_TESTCTRL_SEEK_COUNT,  0, ""               },
 -    {"sorter_mmap",        SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX"           },
 -    {"tune",               SQLITE_TESTCTRL_TUNE,        1, "ID VALUE"       },
 -    };
 -    int testctrl = -1;
 -    int iCtrl = -1;
 -    int rc2 = 0;    /* 0: usage.  1: %d  2: %x  3: no-output */
 -    int isOk = 0;
 -    int i, n2;
 -    const char *zCmd = 0;
 -
 -    if( !ShellHasFlag(p,SHFLG_TestingMode) ){
 -      utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
 -                  "testctrl");
 -      rc = 1;
 -      goto meta_command_exit;
 +#define CTIMEOPT_VAL_(opt) #opt
 +#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
 +#if defined(__clang__) && defined(__clang_major__)
 +  utf8_printf(out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
 +              CTIMEOPT_VAL(__clang_minor__) "."
 +              CTIMEOPT_VAL(__clang_patchlevel__) "\n");
 +#elif defined(_MSC_VER)
 +  utf8_printf(out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
 +#elif defined(__GNUC__) && defined(__VERSION__)
 +  utf8_printf(out, "gcc-" __VERSION__ "\n");
 +#endif
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( vfsinfo ? 1 2 ){
 +  const char *zDbName = nArg==2 ? azArg[1] : "main";
 +  sqlite3_vfs *pVfs = 0;
 +  if( DBX(p) ){
 +    sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
 +    if( pVfs ){
 +      FILE *out = ISS(p)->out;
 +      utf8_printf(out, "vfs.zName      = \"%s\"\n", pVfs->zName);
 +      raw_printf(out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 +      raw_printf(out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 +      raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
      }
 -    open_db(p, 0);
 -    zCmd = nArg>=2 ? azArg[1] : "help";
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( vfslist ? 1 1 ){
 +  sqlite3_vfs *pVfs;
 +  sqlite3_vfs *pCurrent = 0;
 +  FILE *out = ISS(p)->out;
 +  if( DBX(p) ){
 +    sqlite3_file_control(DBX(p), "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
 +  }
 +  for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
 +    utf8_printf(out, "vfs.zName      = \"%s\"%s\n", pVfs->zName,
 +                pVfs==pCurrent ? "  <--- CURRENT" : "");
 +    raw_printf(out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 +    raw_printf(out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 +    raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 +    if( pVfs->pNext ){
 +      raw_printf(out, "-----------------------------------\n");
 +    }
 +  }
 +  return DCR_Ok;
 +}
 +DISPATCHABLE_COMMAND( vfsname ? 0 0 ){
 +  const char *zDbName = nArg==2 ? azArg[1] : "main";
 +  char *zVfsName = 0;
 +  if( DBX(p) ){
 +    sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
 +    if( zVfsName ){
 +      utf8_printf(ISS(p)->out, "%s\n", zVfsName);
 +      sqlite3_free(zVfsName);
 +    }
 +  }
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .width command
 + */
 +static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths){
 +  int j;
 +  p->numWidths = nWidths;
 +  p->pSpecWidths = realloc(p->pSpecWidths, (nWidths+1)*sizeof(int)*2);
 +  if( nWidths>0 ){
 +    shell_check_oom(p->pSpecWidths);
 +    p->pHaveWidths = &p->pSpecWidths[nWidths];
 +    for(j=0; j<nWidths; j++){
 +      p->pSpecWidths[j] = (int)integerValue(azWidths[j]);
 +      p->pHaveWidths[j] = 0;
 +    }
 +  }else p->pHaveWidths = p->pSpecWidths;
 +}
 +COLLECT_HELP_TEXT[
 +  ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
 +  "   Negative values right-justify",
 +];
 +DISPATCHABLE_COMMAND( width ? 1 0 ){
 +  setColumnWidths(p, azArg+1, nArg-1);
 +  return DCR_Ok;
 +}
 +
 +/*****************
 + * The .x, .read and .eval commands
 + * These are together because they share some function and implementation.
 + */
 +CONDITION_COMMAND(read !defined(SQLITE_SHELL_FIDDLE));
 +COLLECT_HELP_TEXT[
 +  ".eval ?ARGS?             Process each ARG's content as shell input.",
 +  ".read FILE               Read input from FILE",
 +  "   If FILE begins with \"|\", it is a command that generates the input.",
 +  ".x ?OBJS or FLAGS?  ...  Excecute content of objects as shell input",
 +  "   FLAGS can be any of {-k -s -f} specifying what subsequent arguments are.",
 +  "   Arguments after -k are keys in a key/value table kept by the shell DB,",
 +  "   for which the object content to be executed is the corresponding value.",
 +  "   Arguments after -f name either files (or pipes), which are to be read",
 +  "   and the content executed. Input pipe names begin with '|'; the rest is",
 +  "   an OS-shell command which can be run by the OS shell to produce output.",
 +  "   Arguments after -s are strings, content of which is to be executed.",
 +  "   The default in effect for arguments prior to any FLAG is -k .",
 +  "   Arguments are executed in order until one fails.",
 +];
 +
 +/* Return an allocated string with trailing whitespace trimmed except
 + * for a trailing newline. If empty (or OOM), return 0. Otherwise, the
 + * caller must eventually pass the return to sqlite3_free().
 + */
 +static char *zPrepForEval(const char *zVal, int ntc){
 +  int ixNewline = 0;
 +  char c;
 +  while( ntc>0 && IsSpace(c = zVal[ntc-1]) ){
 +    if( c=='\n' ) ixNewline = ntc-1;
 +    --ntc;
 +  }
 +  if( ntc>0 ){
 +    /* The trailing newline (or some other placeholder) is important
 +     * because one (or some other character) will likely be put in
 +     * its place during process_input() line/group handling, along
 +     * with a terminating NUL character. Without it, the NULL could
 +     * land past the end of the allocation made just below.
 +     */
 +    int nle = ixNewline>0;
 +    return smprintf( "%.*s%s", ntc, zVal, &"\n"[nle] );
 +  }else{
 +    return 0;
 +  }
 +}
 +
 +/* Evaluate a string as input to the CLI.
 + * zName is the name to be given to the source for error reporting.
 + * Return usual dot command return codes as filtered by process_input().
 + * No provision is made for error emission because, presumably, that
 + * has been done by whatever dot commands or SQL execution is invoked.
 + */
 +static DotCmdRC shellEvalText(char *zIn, const char *zName, ShellExState *psx){
 +  DotCmdRC rv;
 +  ShellInState *psi = ISS(psx);
 +  InSource inRedir
 +    = INSOURCE_STR_REDIR(zIn, zName, psi->pInSource);
 +  psi->pInSource = &inRedir;
 +  rv = process_input(psi);
 +  psi->pInSource = inRedir.pFrom;
 +  return rv;
 +}
  
 -    /* The argument can optionally begin with "-" or "--" */
 -    if( zCmd[0]=='-' && zCmd[1] ){
 -      zCmd++;
 -      if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
 +DISPATCHABLE_COMMAND( eval 3 1 0 ){
 +  DotCmdRC rv = DCR_Ok;
 +  int ia = 1;
 +  int nErrors = 0;
 +  while( ia < nArg ){
 +    char *zA = azArg[ia++];
 +    int nc = strlen30(zA);
 +    char *zSubmit = zPrepForEval(zA, nc);
 +    if( zSubmit ){
 +      char zName[] = "eval arg[999]";
 +      sqlite3_snprintf(sizeof(zName), zName,"eval arg[%d]", ia-1);
 +      rv = shellEvalText(zSubmit, zName, p);
 +      sqlite3_free(zSubmit);
 +      if( rv<DCR_ArgIxMask ){
 +        nErrors += (rv & DCR_Error);
 +        /* Handle DCR_Return, DCR_Exit or DCR_Abort */
 +        if( rv>DCR_Error ) break;
 +      }
 +    }
 +  }
 +  rv |= (nErrors>0);
 +  /* If error to be returned, indicate that complaining about it is done. */
 +  return (rv==DCR_Error)? DCR_CmdErred : rv;
 +}
 +
 +DISPATCHABLE_COMMAND( read 3 2 2 ){
 +  DotCmdRC rc = DCR_Ok;
 +  FILE *inUse = 0;
 +  int (*fCloser)(FILE *) = 0;
 +  if( ISS(p)->bSafeMode ) return DCR_AbortError;
 +  if( azArg[1][0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    *pzErr = smprintf("pipes are not supported in this OS\n");
 +    rc = DCR_Error;
 +    /* p->out = STD_OUT; This was likely not needed. To be investigated. */
 +#else
 +    inUse = popen(azArg[1]+1, "r");
 +    if( inUse==0 ){
 +      *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
 +      rc = DCR_Error;
 +    }else{
 +      fCloser = pclose;
      }
 +#endif
 +  }else if( (inUse = openChrSource(azArg[1]))==0 ){
 +    *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
 +    rc = DCR_Error;
 +  }else{
 +    fCloser = fclose;
 +  }
 +  if( inUse!=0 ){
 +    InSource inSourceRedir
 +      = INSOURCE_FILE_REDIR(inUse, azArg[1], ISS(p)->pInSource);
 +    ISS(p)->pInSource = &inSourceRedir;
 +    rc = process_input(ISS(p));
 +    /* If error(s) occured during process, leave complaining to them. */
 +    if( rc==DCR_Error ) rc = DCR_CmdErred;
 +    assert(fCloser!=0);
 +    fCloser(inUse);
 +    ISS(p)->pInSource = inSourceRedir.pFrom;
 +  }
 +  return rc;
 +}
  
 -    /* --help lists all test-controls */
 -    if( cli_strcmp(zCmd,"help")==0 ){
 -      utf8_printf(p->out, "Available test-controls:\n");
 -      for(i=0; i<ArraySize(aCtrl); i++){
 -        utf8_printf(p->out, "  .testctrl %s %s\n",
 -                    aCtrl[i].zCtrlName, aCtrl[i].zUsage);
 +DISPATCHABLE_COMMAND( x ? 1 0 ){
 +  int ia, nErrors = 0;
 +  sqlite3_stmt *pStmt = 0;
 +  sqlite3 *dbs = p->dbShell;
 +  DotCmdRC rv = DCR_Ok;
 +  enum { AsVar, AsString, AsFile } evalAs = AsVar;
 +
 +  for( ia=1; ia<nArg && nErrors==0; ++ia ){
 +    char *zSubmit = 0;
 +    const char *zOpt = azArg[ia];
 +    if ( *zOpt == '-' ){
 +      static const char *azOpts[] = { "k", "s", "f" };
 +      int io = ArraySize(azOpts);
 +      while( io > 0 ){
 +        if( optionMatch(zOpt, azOpts[--io]) ){
 +          evalAs = io;
 +          zOpt = 0;
 +          break;
 +        }
        }
 -      rc = 1;
 -      goto meta_command_exit;
 +      if( zOpt==0 ) continue;
      }
 -
 -    /* convert testctrl text option to value. allow any unique prefix
 -    ** of the option name, or a numerical value. */
 -    n2 = strlen30(zCmd);
 -    for(i=0; i<ArraySize(aCtrl); i++){
 -      if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
 -        if( testctrl<0 ){
 -          testctrl = aCtrl[i].ctrlCode;
 -          iCtrl = i;
 +    switch( evalAs ){
 +    case AsVar:
 +      if( pStmt==0 ){
 +        int rc;
 +        if( dbs==0 || !shvars_table_exists(dbs) ){
 +          utf8_printf(STD_ERR,
 +                      "\".x vname\" can only be done after .var set ... .\n");
 +          return DCR_Error;
 +        }
 +        rc = sqlite3_prepare_v2(dbs, "SELECT value FROM "SHVAR_TABLE_SNAME
 +                                " WHERE key=$1 AND uses="SPTU_Script,
 +                                -1, &pStmt, 0);
 +        if( rc!=SQLITE_OK ){
 +          utf8_printf(STD_ERR, SHVAR_TABLE_SNAME" is wrongly created.\n");
 +          return DCR_Error;
 +        }
 +      }
 +      if( isalpha(azArg[ia][0]) ){
 +        int rc = sqlite3_reset(pStmt);
 +        rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
 +        rc = sqlite3_step(pStmt);
 +        if( rc==SQLITE_ROW ){
 +          ShellInState *psi = ISS(p);
 +          const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
 +          int nb = sqlite3_column_bytes(pStmt, 0);
 +          zSubmit = zPrepForEval((const char *)zValue, nb);
 +          sqlite3_reset(pStmt); /* End the script read to unlock DB. */
 +          if( zSubmit ){
 +            rv = shellEvalText(zSubmit, azArg[ia], p);
 +            sqlite3_free(zSubmit);
 +          }else{
 +            continue; /* All white (or OOM), ignore. */
 +          }
          }else{
 -          utf8_printf(stderr, "Error: ambiguous test-control: \"%s\"\n"
 -                              "Use \".testctrl --help\" for help\n", zCmd);
 -          rc = 1;
 -          goto meta_command_exit;
 +          utf8_printf(STD_ERR,
 +                      "Skipping var '%s' (not set and executable.)\n",
 +                      azArg[ia]);
 +          ++nErrors;
 +        }
 +      }else{
 +        utf8_printf(STD_ERR,
 +                    "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
 +        ++nErrors;
 +      }
 +      break;
 +    case AsString:
 +      {
 +        zSubmit = zPrepForEval(zOpt, strlen30(zOpt));
 +        if( zSubmit ){
 +          char zName[] = "x arg[999]";
 +          sqlite3_snprintf(sizeof(zName), zName,"x arg[%d]", ia);
 +          rv = shellEvalText(zSubmit, zName, p);
 +          sqlite3_free(zSubmit);
          }
        }
 +      break;
 +    case AsFile:
 +      {
 +        char *av[] = {"read", (char*)zOpt};
 +        rv = readCommand(av, ArraySize(av), p, pzErr);
 +      }
 +      break;
      }
 -    if( testctrl<0 ){
 -      utf8_printf(stderr,"Error: unknown test-control: %s\n"
 -                         "Use \".testctrl --help\" for help\n", zCmd);
 -    }else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){
 -      utf8_printf(stderr,
 -         "line %d: \".testctrl %s\" may not be used in safe mode\n",
 -         p->lineno, aCtrl[iCtrl].zCtrlName);
 -      exit(1);
 +    if( rv<DCR_ArgIxMask ){
 +      nErrors += (rv & DCR_Error);
 +      /* Handle DCR_Return, DCR_Exit or DCR_Abort */
 +      if( rv>DCR_Error ) break;
      }else{
 -      switch(testctrl){
 -
 -        /* sqlite3_test_control(int, db, int) */
 -        case SQLITE_TESTCTRL_OPTIMIZATIONS:
 -          if( nArg==3 ){
 -            unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0);
 -            rc2 = sqlite3_test_control(testctrl, p->db, opt);
 -            isOk = 3;
 -          }
 -          break;
 +      ++nErrors;
 +      rv = DCR_Error;
 +    }
 +  }
 +  sqlite3_finalize(pStmt);
 +  rv |= (nErrors>0);
 +  /* If error to be returned, indicate that complaining about it is done. */
 +  return (rv==DCR_Error)? DCR_CmdErred : rv;
 +}
  
 -        /* sqlite3_test_control(int) */
 -        case SQLITE_TESTCTRL_PRNG_SAVE:
 -        case SQLITE_TESTCTRL_PRNG_RESTORE:
 -        case SQLITE_TESTCTRL_BYTEORDER:
 -          if( nArg==2 ){
 -            rc2 = sqlite3_test_control(testctrl);
 -            isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3;
 -          }
 -          break;
 +/* End of published, standard dot-command implementation functions
 +COMMENT  Build-time overrides of above dot-commands or new dot-commands may be
 +COMMENT  incorporated into shell.c via: -it COMMAND_CUSTOMIZE=<customize source>
 +COMMENT  where <customize source> names a file using the above methodology to
 +COMMENT  define new or altered dot-commands and their help text.
 +*/
 +INCLUDE( COMMAND_CUSTOMIZE );
 +
 +static void DotCommand_destruct(DotCommand *);
 +static const char * DotCommand_name(DotCommand *);
 +static const char * DotCommand_help(DotCommand *, const char *);
 +static DotCmdRC
 +  DotCommand_argsCheck(DotCommand *, char **, int nArgs, char *azArgs[]);
 +static DotCmdRC
 +  DotCommand_execute(DotCommand *, ShellExState *, char **, int, char *[]);
 +
 +static VTABLE_NAME(DotCommand) dot_cmd_VtabBuiltIn = {
 +  DotCommand_destruct,
 +  DotCommand_name,
 +  DotCommand_help,
 +  DotCommand_argsCheck,
 +  DotCommand_execute
 +};
  
 -        /* sqlite3_test_control(int, uint) */
 -        case SQLITE_TESTCTRL_PENDING_BYTE:
 -          if( nArg==3 ){
 -            unsigned int opt = (unsigned int)integerValue(azArg[2]);
 -            rc2 = sqlite3_test_control(testctrl, opt);
 -            isOk = 3;
 -          }
 -          break;
 +/* Define and populate command dispatch table. */
 +static struct CommandInfo {
 +  VTABLE_NAME(DotCommand) *mcVtabBuiltIn;
 +  const char * cmdName;
 +  DotCmdRC (*cmdDoer)(char *azArg[], int nArg,
 +                               ShellExState *, char **pzErr);
 +  unsigned char minLen, minArgs, maxArgs;
 +  const char *azHelp[2]; /* primary and secondary help text */
 +  void * pCmdData;
 +  } command_table[] = {
 +  COMMENT Emit the dispatch table entries generated and collected above.
 +#define DOT_CMD_INFO(cmd, nlenMin, minArgs, maxArgs) \
 +  &dot_cmd_VtabBuiltIn, #cmd, cmd ## Command, nlenMin, minArgs, maxArgs
 +  EMIT_DOTCMD_INIT(2);
 +#undef DOT_CMD_INFO
 +  { 0, 0, 0, 0, (u8)~0, (u8)~0, {0,0}, 0 }
 +};
 +static unsigned numCommands
 +  = sizeof(command_table)/sizeof(struct CommandInfo) - 1;
  
 -        /* sqlite3_test_control(int, int, sqlite3*) */
 -        case SQLITE_TESTCTRL_PRNG_SEED:
 -          if( nArg==3 || nArg==4 ){
 -            int ii = (int)integerValue(azArg[2]);
 -            sqlite3 *db;
 -            if( ii==0 && cli_strcmp(azArg[2],"random")==0 ){
 -              sqlite3_randomness(sizeof(ii),&ii);
 -              printf("-- random seed: %d\n", ii);
 -            }
 -            if( nArg==3 ){
 -              db = 0;
 -            }else{
 -              db = p->db;
 -              /* Make sure the schema has been loaded */
 -              sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
 -            }
 -            rc2 = sqlite3_test_control(testctrl, ii, db);
 -            isOk = 3;
 -          }
 -          break;
 +static DotCommand *builtInCommand(int ix){
 +  if( ix<0 || (unsigned)ix>=numCommands ) return 0;
 +  return (DotCommand *)&command_table[ix];
 +}
  
 -        /* sqlite3_test_control(int, int) */
 -        case SQLITE_TESTCTRL_ASSERT:
 -        case SQLITE_TESTCTRL_ALWAYS:
 -          if( nArg==3 ){
 -            int opt = booleanValue(azArg[2]);
 -            rc2 = sqlite3_test_control(testctrl, opt);
 -            isOk = 1;
 -          }
 -          break;
 +static void DotCommand_destruct(DotCommand *pMe){
 +  UNUSED_PARAMETER(pMe);
 +}
  
 -        /* sqlite3_test_control(int, int) */
 -        case SQLITE_TESTCTRL_LOCALTIME_FAULT:
 -        case SQLITE_TESTCTRL_NEVER_CORRUPT:
 -          if( nArg==3 ){
 -            int opt = booleanValue(azArg[2]);
 -            rc2 = sqlite3_test_control(testctrl, opt);
 -            isOk = 3;
 -          }
 -          break;
 +static const char * DotCommand_name(DotCommand *pMe){
 +  return ((struct CommandInfo *)pMe)->cmdName;
 +}
  
 -        /* sqlite3_test_control(sqlite3*) */
 -        case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
 -          rc2 = sqlite3_test_control(testctrl, p->db);
 -          isOk = 3;
 -          break;
 +static const char * DotCommand_help(DotCommand *pMe, const char * zWhat){
 +  struct CommandInfo *pci = (struct CommandInfo *)pMe;
 +  if( zWhat==0 ) return pci->azHelp[0];
 +  if( *zWhat==0 ) return pci->azHelp[1];
 +  else return 0;
 +}
  
 -        case SQLITE_TESTCTRL_IMPOSTER:
 -          if( nArg==5 ){
 -            rc2 = sqlite3_test_control(testctrl, p->db,
 -                          azArg[2],
 -                          integerValue(azArg[3]),
 -                          integerValue(azArg[4]));
 -            isOk = 3;
 -          }
 -          break;
 +static DotCmdRC
 +  DotCommand_argsCheck(DotCommand *pMe,
 +                        char **pzErrMsg, int nArgs, char *azArgs[]){
 +  struct CommandInfo *pci = (struct CommandInfo *)pMe;
 +  UNUSED_PARAMETER(azArgs);
 +  if( pci->minArgs > nArgs ){
 +    if( pzErrMsg ){
 +      *pzErrMsg = smprintf("Too few arguments for \".%s\", need %d\n",
 +                           azArgs[0], pci->minArgs-1);
 +    }
 +    return DCR_TooFew;
 +  }else if( pci->maxArgs > 0 && pci->maxArgs < nArgs ){
 +    if( pzErrMsg ){
 +      *pzErrMsg = smprintf("Too many arguments for \".%s\", over %d\n",
 +                           azArgs[0], pci->maxArgs-1);
 +    }
 +    return DCR_TooMany;
 +  }else return DCR_Ok;
 +}
  
 -        case SQLITE_TESTCTRL_SEEK_COUNT: {
 -          u64 x = 0;
 -          rc2 = sqlite3_test_control(testctrl, p->db, &x);
 -          utf8_printf(p->out, "%llu\n", x);
 -          isOk = 3;
 -          break;
 -        }
 +static DotCmdRC
 +  DotCommand_execute(DotCommand *pMe, ShellExState *pssx,
 +                      char **pzErrMsg, int nArgs, char *azArgs[]){
 +  return (((struct CommandInfo *)pMe)->cmdDoer)(azArgs, nArgs, pssx, pzErrMsg);
 +}
  
 -#ifdef YYCOVERAGE
 -        case SQLITE_TESTCTRL_PARSER_COVERAGE: {
 -          if( nArg==2 ){
 -            sqlite3_test_control(testctrl, p->out);
 -            isOk = 3;
 -          }
 -          break;
 -        }
 +/*****************
 +** DotCommand iteration by name match, used by the .help dot-command.
 +** DotCommands, or their ad-hoc stand-ins, having matching names are produced
 +** in lexical order, with the iterator indicating which has been produced.
 +** If .zAdhocHelpName == 0, it is a regular DotCommand. Otherwise, the
 +** ".unknown" DotCommand is returned, whose help() method is to be used.
 +** Any returned CmdMatchIter must eventually be passed to freeCmdMatchIter().
 +*/
 +typedef struct CmdMatchIter {
 +  ShellExState *psx;
 +  /* 0 indicates prepared statement; non-0 is the glob pattern. */
 +  const char *zPattern;
 +  union {
 +    DotCommand *pDotCmd;
 +#if SHELL_DYNAMIC_EXTENSION
 +    sqlite3_stmt *stmt;
  #endif
 -#ifdef SQLITE_DEBUG
 -        case SQLITE_TESTCTRL_TUNE: {
 -          if( nArg==4 ){
 -            int id = (int)integerValue(azArg[2]);
 -            int val = (int)integerValue(azArg[3]);
 -            sqlite3_test_control(testctrl, id, &val);
 -            isOk = 3;
 -          }else if( nArg==3 ){
 -            int id = (int)integerValue(azArg[2]);
 -            sqlite3_test_control(testctrl, -id, &rc2);
 -            isOk = 1;
 -          }else if( nArg==2 ){
 -            int id = 1;
 -            while(1){
 -              int val = 0;
 -              rc2 = sqlite3_test_control(testctrl, -id, &val);
 -              if( rc2!=SQLITE_OK ) break;
 -              if( id>1 ) utf8_printf(p->out, "  ");
 -              utf8_printf(p->out, "%d: %d", id, val);
 -              id++;
 -            }
 -            if( id>1 ) utf8_printf(p->out, "\n");
 -            isOk = 3;
 -          }
 -          break;
 -        }
 +  } cursor;
 +#if SHELL_DYNAMIC_EXTENSION
 +  char *zAdhocHelpText; /* registered extension ad-hoc help */
  #endif
 -        case SQLITE_TESTCTRL_SORTER_MMAP:
 -          if( nArg==3 ){
 -            int opt = (unsigned int)integerValue(azArg[2]);
 -            rc2 = sqlite3_test_control(testctrl, p->db, opt);
 -            isOk = 3;
 -          }
 -          break;
 -      }
 -    }
 -    if( isOk==0 && iCtrl>=0 ){
 -      utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
 -      rc = 1;
 -    }else if( isOk==1 ){
 -      raw_printf(p->out, "%d\n", rc2);
 -    }else if( isOk==2 ){
 -      raw_printf(p->out, "0x%08x\n", rc2);
 -    }
 -  }else
 -#endif /* !defined(SQLITE_UNTESTABLE) */
 +} CmdMatchIter;
 +
 +/* Release resources held by the iterator and clear it. */
 +static void freeCmdMatchIter(CmdMatchIter *pMMI){
 +  if( pMMI->zPattern!=0 ){
 +    sqlite3_free((void *)pMMI->zPattern);
 +    pMMI->zPattern = 0;
 +    pMMI->cursor.pDotCmd = 0;
 +  }
 +#if SHELL_DYNAMIC_EXTENSION
 +  else{
 +    sqlite3_finalize(pMMI->cursor.stmt);
 +    pMMI->cursor.stmt = 0;
 +  }
 +  sqlite3_free(pMMI->zAdhocHelpText);
 +  pMMI->zAdhocHelpText = 0;
 +#endif
 +}
  
 -  if( c=='t' && n>4 && cli_strncmp(azArg[0], "timeout", n)==0 ){
 -    open_db(p, 0);
 -    sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0);
 +/* Prepare an iterator that will produce a sequence of DotCommand
 + * pointers whose referents' names match the given cmdFragment.
 + * Return how many will match (if iterated upon return.) */
 +static int findMatchingDotCmds(const char *cmdFragment,
 +                                CmdMatchIter *pMMI,
 +                                ShellExState *psx){
 +  CmdMatchIter mmi = { psx, 0, 0 };
 +  int rv = 0;
 +#if SHELL_DYNAMIC_EXTENSION
 +  if( ISS(psx)->bDbDispatch ){
 +    sqlite3_stmt *stmtCount = 0;
 +    /* Prepare rv.stmt to yield results glob-matching cmdFragment. */
 +    static const char * const zSqlIter =
 +      "SELECT name, extIx, cmdIx, help FROM "SHELL_HELP_VIEW" "
 +      "WHERE name glob (?||'*') ORDER BY name";
 +    static const char * const zSqlCount =
 +      "SELECT count(*) FROM "SHELL_HELP_VIEW" "
 +      "WHERE name glob (?||'*')";
 +    if( pMMI ){
 +      sqlite3_prepare_v2(psx->dbShell, zSqlIter, -1, &mmi.cursor.stmt, 0);
 +      sqlite3_bind_text(mmi.cursor.stmt, 1, cmdFragment? cmdFragment:"", -1, 0);
 +    }
 +    sqlite3_prepare_v2(psx->dbShell, zSqlCount, -1, &stmtCount, 0);
 +    sqlite3_bind_text(stmtCount, 1, cmdFragment? cmdFragment : "", -1, 0);
 +    if( SQLITE_ROW==sqlite3_step(stmtCount) ){
 +      rv = sqlite3_column_int(stmtCount, 0);
 +    }else assert(0);
 +    sqlite3_finalize(stmtCount);
    }else
 +#endif
 +  {
 +    int i = 0;
 +    mmi.zPattern = smprintf("%s*", cmdFragment? cmdFragment : "");
 +    shell_check_oom((void *)mmi.zPattern);
 +
 +    struct CommandInfo *pCI = command_table;
 +    mmi.cursor.pDotCmd = (DotCommand *)command_table;
 +    while( pCI<command_table+numCommands ){
 +      if( sqlite3_strglob(mmi.zPattern, pCI->cmdName)==0 ) ++rv;
 +      ++pCI;
 +    }
 +  }
 +  if( pMMI ) *pMMI = mmi;
 +  else freeCmdMatchIter(&mmi);
 +  return rv;
 +}
  
 -  if( c=='t' && n>=5 && cli_strncmp(azArg[0], "timer", n)==0 ){
 -    if( nArg==2 ){
 -      enableTimer = booleanValue(azArg[1]);
 -      if( enableTimer && !HAS_TIMER ){
 -        raw_printf(stderr, "Error: timer not available on this system.\n");
 -        enableTimer = 0;
 +/* Produce the next DotCommand pointer from the iterator, or 0 if no next. */
 +static DotCommand * nextMatchingDotCmd(CmdMatchIter *pMMI){
 +  DotCommand *rv = 0;
 +#if SHELL_DYNAMIC_EXTENSION
 +  if( pMMI->zPattern==0 ){
 +    int rc = sqlite3_step(pMMI->cursor.stmt);
 +    if( rc==SQLITE_ROW ){
 +      /* name, extIx, cmdIx, help */
 +      int extIx = sqlite3_column_int(pMMI->cursor.stmt, 1);
 +      int cmdIx = sqlite3_column_int(pMMI->cursor.stmt, 2);
 +      ShellInState *psi = ISS(pMMI->psx);
 +      sqlite3_free(pMMI->zAdhocHelpText);
 +      if( cmdIx>=0 ){
 +        pMMI->zAdhocHelpText = 0;
 +        return command_by_index(psi, extIx, cmdIx);
 +      }else{
 +        const unsigned char *zHT = sqlite3_column_text(pMMI->cursor.stmt, 3);
 +        assert(psi->pUnknown!=0);
 +        assert(extIx<psi->numExtLoaded && extIx>0);
 +        if( zHT==0 ) zHT = sqlite3_column_text(pMMI->cursor.stmt, 0);
 +        pMMI->zAdhocHelpText = sqlite3_mprintf("%s", zHT);
 +        return psi->pShxLoaded[extIx].pUnknown;
        }
      }else{
 -      raw_printf(stderr, "Usage: .timer on|off\n");
 -      rc = 1;
 +      sqlite3_finalize(pMMI->cursor.stmt);
 +      pMMI->cursor.stmt = 0;
      }
    }else
 +#endif
 +  {
 +    struct CommandInfo *pCI = (struct CommandInfo *)(pMMI->cursor.pDotCmd);
 +    assert(pCI>=command_table && pCI<=command_table+numCommands);
 +    while( pCI<command_table+numCommands ){
 +      if( sqlite3_strglob(pMMI->zPattern, pCI->cmdName)==0 ){
 +      rv = pMMI->cursor.pDotCmd;
 +      }
 +      pMMI->cursor.pDotCmd = (DotCommand *)(++pCI);
 +      if( rv!=0 ) break;
 +    }
 +  }
 +  return rv;
 +}
  
 -#ifndef SQLITE_OMIT_TRACE
 -  if( c=='t' && cli_strncmp(azArg[0], "trace", n)==0 ){
 -    int mType = 0;
 -    int jj;
 -    open_db(p, 0);
 -    for(jj=1; jj<nArg; jj++){
 -      const char *z = azArg[jj];
 -      if( z[0]=='-' ){
 -        if( optionMatch(z, "expanded") ){
 -          p->eTraceType = SHELL_TRACE_EXPANDED;
 -        }
 -#ifdef SQLITE_ENABLE_NORMALIZE
 -        else if( optionMatch(z, "normalized") ){
 -          p->eTraceType = SHELL_TRACE_NORMALIZED;
 -        }
 +/*****************
 +** DotCommand lookup
 +**
 +** For the non-extended or non-extensible shell, this function does
 +** a binary search of the fixed list of dot-command info structs.
 +** For an extended shell, it queries the shell's DB. Either way,
 +** this function returns a DotCommand pointer if one can be found
 +** with an adequate match for the given name. Here, "adequate" may
 +** vary according to whether shell extensions have been loaded. If
 +** not, the match must be for as many characters as set within the
 +** above CommandInfo array (set via DISPATCHABLE_COMMAND macro call.)
 +** If shell extensions are loaded, the match must be long enough to
 +** result in a unique lookup.
 +*/
 +DotCommand *findDotCommand(const char *cmdName, ShellExState *psx,
 +                             /* out */ int *pnFound){
 +  if( pnFound ) *pnFound = 0;
 +#if SHELL_DYNAMIC_EXTENSION
 +  if( ISS(psx)->bDbDispatch ){
 +    int rc;
 +    int extIx = -1, cmdIx = -1, nf = 0;
 +    sqlite3_stmt *pStmt = 0;
 +    const char *zSql = "SELECT COUNT(*), extIx, cmdIx"
 +      " FROM "SHELL_DISP_VIEW" WHERE name glob (?||'*')";
 +    rc = sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0);
 +    sqlite3_bind_text(pStmt, 1, cmdName, -1, 0);
 +    rc = sqlite3_step(pStmt);
 +    nf = sqlite3_column_int(pStmt, 0);
 +    extIx = sqlite3_column_int(pStmt, 1);
 +    cmdIx = sqlite3_column_int(pStmt, 2);
 +    sqlite3_finalize(pStmt);
 +    if( rc!= SQLITE_ROW ) return 0;
 +    if( pnFound ) *pnFound = nf;
 +    if( nf!=1 ) return 0; /* Future: indicate collisions if > 1 */
 +    return (cmdIx<0)? 0 : command_by_index(ISS(psx), extIx, cmdIx);
 +  }else
  #endif
 -        else if( optionMatch(z, "plain") ){
 -          p->eTraceType = SHELL_TRACE_PLAIN;
 -        }
 -        else if( optionMatch(z, "profile") ){
 -          mType |= SQLITE_TRACE_PROFILE;
 -        }
 -        else if( optionMatch(z, "row") ){
 -          mType |= SQLITE_TRACE_ROW;
 -        }
 -        else if( optionMatch(z, "stmt") ){
 -          mType |= SQLITE_TRACE_STMT;
 -        }
 -        else if( optionMatch(z, "close") ){
 -          mType |= SQLITE_TRACE_CLOSE;
 -        }
 -        else {
 -          raw_printf(stderr, "Unknown option \"%s\" on \".trace\"\n", z);
 -          rc = 1;
 -          goto meta_command_exit;
 -        }
 +  {
 +    int cmdLen = strlen30(cmdName);
 +    struct CommandInfo *pci = 0;
 +    int ixb = 0, ixe = numCommands-1;
 +    while( ixb <= ixe ){
 +      int ixm = (ixb+ixe)/2;
 +      int md = cli_strncmp(cmdName, command_table[ixm].cmdName, cmdLen);
 +      if( md>0 ){
 +        ixb = ixm+1;
 +      }else if( md<0 ){
 +        ixe = ixm-1;
        }else{
 -        output_file_close(p->traceOut);
 -        p->traceOut = output_file_open(z, 0);
 +        /* Have a match, see whether it's ambiguous. */
 +        if( command_table[ixm].minLen > cmdLen ){
 +          if( (ixm>0
 +               && !cli_strncmp(cmdName, command_table[ixm-1].cmdName, cmdLen))
 +              ||
 +              (ixm<ixe
 +               && !cli_strncmp(cmdName, command_table[ixm+1].cmdName, cmdLen))){
 +            /* Yes, a neighbor matches too. */
 +            if( pnFound ) *pnFound = 2; /* could be more, but >1 suffices */
 +            return 0;
 +          }
 +        }
 +        pci = &command_table[ixm];
 +        if( pnFound ) *pnFound = 1;
 +        break;
        }
      }
 -    if( p->traceOut==0 ){
 -      sqlite3_trace_v2(p->db, 0, 0, 0);
 -    }else{
 -      if( mType==0 ) mType = SQLITE_TRACE_STMT;
 -      sqlite3_trace_v2(p->db, mType, sql_trace_callback, p);
 -    }
 -  }else
 -#endif /* !defined(SQLITE_OMIT_TRACE) */
 +    if( pnFound && pci ) *pnFound = 1;
 +    return (DotCommand *)pci;
 +  }
 +}
  
 -#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE)
 -  if( c=='u' && cli_strncmp(azArg[0], "unmodule", n)==0 ){
 -    int ii;
 -    int lenOpt;
 -    char *zOpt;
 -    if( nArg<2 ){
 -      raw_printf(stderr, "Usage: .unmodule [--allexcept] NAME ...\n");
 +/*
 +** Given a DotCommand, desired help level,
 +** ( possibly retreived ad-hoc help text for extensible shell, )
 +** and an optional all-text search pattern, then
 +**   when level==0 and primary help available, output it
 +**   when level==1 and primary or secondary help available, output it
 +**   when level==2 and any help text matches pattern, output it
 +**   when level>2 or no pattern: output all help text
 +** If cLead==0, anything meeting above criteria is output. Otherwise, output
 +** is restricted to those commands whose primary help begins with cLead.
 +** Return 1 if anything output, else 0.
 +*/
 +static int putSelectedCmdHelp(DotCommand *pmc, int iLevel, char cLead,
 +#if SHELL_DYNAMIC_EXTENSION
 +                              const char *zHelpText,
 +#endif
 +                              FILE *out, const char *zSearch){
 +  int rc = 0;
 +  assert(pmc!=0);
 +#if SHELL_DYNAMIC_EXTENSION
 +  if( zHelpText ){
 +    const char *zHT = zHelpText+1; /* skip over classifier */
 +    if( cLead && *zHelpText!= cLead ) return 0;
 +    if( *zHelpText==0 ) return 0;
 +    const char *zLE = zHT;
 +    switch( iLevel ){
 +    case 0:
 +      while( *zLE && *zLE++!='\n' ) {}
 +      utf8_printf(out,".%.*s", (int)(zLE-zHT), zHT);
        rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    zOpt = azArg[1];
 -    if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++;
 -    lenOpt = (int)strlen(zOpt);
 -    if( lenOpt>=3 && cli_strncmp(zOpt, "-allexcept",lenOpt)==0 ){
 -      assert( azArg[nArg]==0 );
 -      sqlite3_drop_modules(p->db, nArg>2 ? (const char**)(azArg+2) : 0);
 -    }else{
 -      for(ii=1; ii<nArg; ii++){
 -        sqlite3_create_module(p->db, azArg[ii], 0, 0);
 +      break;
 +    case 2:
 +      if( zSearch ){
 +        if( !sqlite3_strlike(zSearch, zHT, 0) ) break;
        }
-       /* else fall thru */
++      deliberate_fall_through;
 +    case 1:
 +    default:
 +      utf8_printf(out,".%s", zHT);
 +      rc = 1;
      }
    }else
  #endif
 -
 -#if SQLITE_USER_AUTHENTICATION
 -  if( c=='u' && cli_strncmp(azArg[0], "user", n)==0 ){
 -    if( nArg<2 ){
 -      raw_printf(stderr, "Usage: .user SUBCOMMAND ...\n");
 -      rc = 1;
 -      goto meta_command_exit;
 -    }
 -    open_db(p, 0);
 -    if( cli_strcmp(azArg[1],"login")==0 ){
 -      if( nArg!=4 ){
 -        raw_printf(stderr, "Usage: .user login USER PASSWORD\n");
 -        rc = 1;
 -        goto meta_command_exit;
 -      }
 -      rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
 -                                     strlen30(azArg[3]));
 -      if( rc ){
 -        utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]);
 +  {
 +    const char *zHTp = pmc->pMethods->help(pmc, 0);
 +    const char *zHTs = pmc->pMethods->help(pmc, "");
 +    if( !zHTp && !zHTs ) return 0;
 +    if( cLead && zHTp && *zHTp!= cLead ) return 0;
 +    switch( iLevel ){
 +    case 0:
 +    case 1:
 +      if( zHTp ){
 +        utf8_printf(out, HELP_TEXT_FMTP, zHTp+1);
          rc = 1;
        }
 -    }else if( cli_strcmp(azArg[1],"add")==0 ){
 -      if( nArg!=5 ){
 -        raw_printf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n");
 +      if( iLevel>0 && zHTs ){
 +        utf8_printf(out, HELP_TEXT_FMTS, zHTs);
          rc = 1;
 -        goto meta_command_exit;
        }
 -      rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
 -                            booleanValue(azArg[4]));
 -      if( rc ){
 -        raw_printf(stderr, "User-Add failed: %d\n", rc);
 -        rc = 1;
 +      break;
 +    case 2:
 +      if( zSearch ){
 +        int m = 0;
 +        if( zHTp && !sqlite3_strlike(zSearch, zHTp, 0) ) ++m;
 +        if( zHTs && !sqlite3_strlike(zSearch, zHTs, 0) ) ++m;
 +        if( m==0 ) break;
        }
-       /* else fall thru */
 -    }else if( cli_strcmp(azArg[1],"edit")==0 ){
 -      if( nArg!=5 ){
 -        raw_printf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n");
 -        rc = 1;
 -        goto meta_command_exit;
++      deliberate_fall_through;
 +    default:
 +      if( zHTp ) utf8_printf(out, HELP_TEXT_FMTP, zHTp+1);
 +      if( zHTs ) utf8_printf(out, HELP_TEXT_FMTS, zHTs);
 +      rc = 1;
 +    }
 +  }
 +  return rc;
 +}
 +
 +/*
 +** Output primary (single-line) help for a known command.
 +*/
 +static void showPrimaryHelp(FILE *out, const char *zCmd, ShellExState *psx){
 +  CmdMatchIter cmi = {0};
 +  int nm = findMatchingDotCmds(zCmd, &cmi, psx);
 +  DotCommand *pdc = nextMatchingDotCmd(&cmi);
 +  if( pdc!=0 ){
 +    putSelectedCmdHelp(pdc, 0, 0,
 +#if SHELL_DYNAMIC_EXTENSION
 +                       cmi.zAdhocHelpText,
 +#endif
 +                       out, 0);
 +  }
 +  freeCmdMatchIter(&cmi);
 +}
 +
 +/*
 +** Output various subsets of help text. These 6 are defined:
 +** 1. HO_AllP  For all commands, primary help text only.
 +** 2. HO_AllX  For all commands, complete help text.
 +** 3. HO_LikeP For multiple commands matching pattern, primary help text only.
 +** 4. HO_OneX  For a single matched command, complete help text.
 +** 5. HO_LikeT For commands whose help contains a pattern, complete help text.
 +** 6. HO_Undoc For all internal "undocumented" (without normal help) commands.
 +** These variations are indicated thusly:
 +** 1. zPattern is NULL
 +** 2. zPattern is ""
 +** 3. zPattern is a prefix matching more than one command
 +** 4. zPattern is a word or prefix matching just one command
 +** 5. zPattern is neither case 3 or 4 but is found in complete help text
 +** 6. zPattern is exactly the pointer known locally as zHelpAll.
 +**
 +** Return the number of matches.
 +*/
 +static int showHelp(FILE *out, const char *zPattern, ShellExState *psx){
 +  u8 bNullPattern = zPattern==0;
 +  u8 bShowUndoc = zPattern==zHelpAll;
 +  u8 bEmptyPattern = !bNullPattern && (*zPattern==0 || bShowUndoc);
 +  int npm = 0; /* track how many matches found */
 +  CmdMatchIter cmi = {0};
 +  DotCommand *pdc;
 +  char *zPat = 0;
 +  char cLead = (bShowUndoc)? ',' : '.';
 +  int iLevel = 0;
 +  enum {
 +    HO_Tbd, HO_AllP, HO_AllX, HO_LikeP, HO_OneX, HO_LikeT, HO_Undoc
 +  } hoKind = bShowUndoc? HO_Undoc : HO_Tbd;
 +
 +  if( hoKind==HO_Undoc ){
 +    unsigned ixct = 0;
 +    utf8_printf(out, "%s\n%s\n",
 +      "The following commands are for SQLite diagnosis and internal testing.",
 +      "They are undocumented and subject to change without notice.");
 +    /* Bypass command lookup/resolution. This is just for internal commands. */
 +    while( ixct<numCommands ){
 +      struct CommandInfo *pci = &command_table[ixct];
 +      const char *zH = pci->azHelp[0];
 +      if( zH && *zH==cLead ){
 +        utf8_printf(out, HELP_TEXT_FMTP, zH+1);
 +        zH = pci->azHelp[1];
 +        if( zH ) utf8_printf(out, HELP_TEXT_FMTS, zH);
 +        ++npm;
 +      }
 +      ++ixct;
 +    }
 +    return npm;
 +  }
 +  npm = findMatchingDotCmds(zPattern, &cmi, psx);
 +  if( bNullPattern ) hoKind = HO_AllP;
 +  else if( bEmptyPattern ) hoKind = HO_AllX;
 +  else if( npm>1 ) hoKind = HO_LikeP;
 +  else if( npm==1 ) hoKind = HO_OneX;
 +  else{
 +    hoKind = HO_LikeT;
 +    zPat = smprintf("%%%s%%", zPattern);
 +    shell_check_oom(zPat);
 +  }
 +  zPattern = 0;
 +  iLevel = 1;
 +  switch( hoKind ){
 +  case HO_AllP: case HO_LikeP:
 +    iLevel = 0;
 +    break;
 +  case HO_AllX:
 +    break;
 +  case HO_OneX:
 +    cLead = 0;
 +    break;
 +  case HO_LikeT:
 +    zPattern = zPat;
 +    break;
 +  default: return 0;
 +  }
 +  npm = 0;
 +  while( 0 != (pdc = nextMatchingDotCmd(&cmi)) ){
 +    npm += putSelectedCmdHelp(pdc, iLevel, cLead,
 +#if SHELL_DYNAMIC_EXTENSION
 +                              cmi.zAdhocHelpText,
 +#endif
 +                              out, zPattern);
 +  }
 +  freeCmdMatchIter(&cmi);
 +  sqlite3_free(zPat);
 +  return npm;
 +}
 +
 +/* Perform preparation needed prior to actually running any dot command. */
 +static void command_prep(ShellInState *psi){
 +  clearTempFile(psi);
 +#ifndef SQLITE_OMIT_VIRTUALTABLE
 +  if( psi->expert.pExpert ){
 +    expertFinish(psi, 1, 0);
 +  }
 +#endif
 +}
 +
 +/* Perform post-execution actions needed after running any dot command. */
 +static void command_post(ShellInState *psi){
 +  if( psi->outCount ){
 +    psi->outCount--;
 +    if( psi->outCount==0 ) output_reset(psi);
 +  }
 +  updateSafeMode(psi);
 +}
 +
 +/* Issue errors per returned DotCmdRC and error message, handle certain
 + * exceptional returns, and translate return to the sanitized first 8.
 + */
 +static DotCmdRC dot_command_errors(char *zErr, char *azArg[], int nArg,
 +                                    DotCmdRC dcr, ShellExState *psx){
 +  if( psx->shellAbruptExit!=0 ){
 +    if( psx->shellAbruptExit>0x1ff ) dcr = DCR_AbortError;
 +    else dcr = DCR_Exit | (dcr & DCR_Error);
 +  }
 +  if( dcr==DCR_CmdErred ){
 +    /* Error message(s) already emitted. Just translate to execute error. */
 +    dcr = DCR_Error;
 +  }else if( dcr==DCR_SayUsage ){
 +    if( zErr ){
 +      utf8_printf(STD_ERR, "%s", zErr);
 +    }else{
 +      utf8_printf(STD_ERR, "Usage:\n");
 +      showPrimaryHelp(STD_ERR, azArg[0], psx);
 +    }
 +    dcr = DCR_Error;
 +  }else if( dcr > DCR_AbortError ){
 +    /* Handle invocation errors. */
 +    int ia = dcr & DCR_ArgIxMask;
 +    const char *pArg1st = (ia>=nArg)? "" : azArg[ia];
 +    const char *pArg2nd = 0;
 +    int ec = dcr & ~DCR_ArgIxMask;
 +    int unknownCmd = 0;
 +    const char *z = 0;
 +    switch( ec ){
 +    case DCR_Unknown:
 +      unknownCmd = ia==0;
 +      if( unknownCmd ) z = "unknown dot command: \".%s\"";
 +      else{
 +        z = "On .%s, unknown option or subcommand: \"%s\"";
 +        pArg2nd = pArg1st;
 +        pArg1st = azArg[0];
        }
 -      rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
 -                              booleanValue(azArg[4]));
 -      if( rc ){
 -        raw_printf(stderr, "User-Edit failed: %d\n", rc);
 -        rc = 1;
 +      break;
 +    case DCR_Ambiguous:
 +      if( ia>0 ){
 +        z = "For .%s, \"%s\" is ambiguous; it matches more than one subcommand";
 +        pArg2nd = pArg1st;
 +        pArg1st = azArg[0];
 +      }else z = "\"%s\" is ambiguous; it matches multiple dot commands";
 +      break;
 +    case DCR_Unpaired:
 +      z = "With .%s, option %s must be paired with a value argument";
 +      pArg2nd = pArg1st;
 +      pArg1st = azArg[0];
 +      break;
 +    case DCR_TooMany:
 +      z = "Excess arguments provided; try .help %s";
 +      pArg1st = azArg[0];
 +      break;
 +    case DCR_TooFew:
 +      z = "Insufficient arguments; try .help %s";
 +      pArg1st = azArg[0];
 +      break;
 +    case DCR_Missing:
 +      z = "Required arguments missing; try .help %s";
 +      pArg1st = azArg[0];
 +      break;
 +    case DCR_ArgWrong:
 +      z = "Argument(s) incorrect; try .help %s";
 +      pArg1st = azArg[0];
 +      break;
 +    default:
 +      z = "? Bizarre return from %s";
 +      break;
 +    }
 +    if( !zErr ){
 +      if( z!=0 ){
 +        char *ze = smprintf("Error: %z\n", smprintf(z, pArg1st, pArg2nd));
 +        utf8_printf(STD_ERR, "%s", ze);
 +        sqlite3_free(ze);
        }
 -    }else if( cli_strcmp(azArg[1],"delete")==0 ){
 -      if( nArg!=3 ){
 -        raw_printf(stderr, "Usage: .user delete USER\n");
 -        rc = 1;
 -        goto meta_command_exit;
 +    }else{
 +      const char *fmt
 +        = (sqlite3_strnicmp(zErr, "Error", 5)==0)? "%s" : "Error: %s";
 +      utf8_printf(STD_ERR, fmt, zErr);
 +    }
 +    if( INSOURCE_IS_INTERACTIVE(ISS(psx)->pInSource) ){
 +      if( unknownCmd ){
 +        utf8_printf(STD_ERR, "Enter \".help\" for a list of commands.\n");
 +      }else{
 +        utf8_printf(STD_ERR, "Usage:\n");
 +        showPrimaryHelp(STD_ERR, azArg[0], psx);
        }
 -      rc = sqlite3_user_delete(p->db, azArg[2]);
 -      if( rc ){
 -        raw_printf(stderr, "User-Delete failed: %d\n", rc);
 -        rc = 1;
 +    }
 +    /* If the shell DB becomes messed up, at least .quit will be doable. */
 +    if( unknownCmd && psx->dbShell!=0
 +        && sqlite3_strnicmp(azArg[0],"quit",strlen30(azArg[0]))==0 ){
 +      dcr = DCR_Return;
 +    }else{
 +      dcr = DCR_Error;
 +    }
 +  }else{
 +    /* Handle execution errors. */
 +    if( dcr==DCR_AbortError ){
 +      if( zErr ){
 +        utf8_printf(STD_ERR, "Error: \".%s\" may not %s in -safe mode\n",
 +                    azArg[0], zErr);
 +      }else {
 +        utf8_printf(STD_ERR, "Error: \".%s\" forbidden in -safe mode\n",
 +                    azArg[0]);
        }
 +      psx->shellAbruptExit = 0x203;
      }else{
 -      raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n");
 -      rc = 1;
 -      goto meta_command_exit;
 +      int error = dcr & DCR_Error;
 +      int action = dcr & ~DCR_Error;
 +      if( error ){
 +        if( zErr ){
 +          const char *fmt
 +            = (sqlite3_strnicmp(zErr, "Error", 5)==0)? "%s" : "Error: %s";
 +          utf8_printf(STD_ERR, fmt, zErr);
 +        }
 +        else utf8_printf(STD_ERR, "Error: .%s failed\n", azArg[0]);
 +      }
      }
 -  }else
 -#endif /* SQLITE_USER_AUTHENTICATION */
 +  }
 +  return dcr;
 +}
  
 -  if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){
 -    utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
 -        sqlite3_libversion(), sqlite3_sourceid());
 -#if SQLITE_HAVE_ZLIB
 -    utf8_printf(p->out, "zlib version %s\n", zlibVersion());
 -#endif
 -#define CTIMEOPT_VAL_(opt) #opt
 -#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
 -#if defined(__clang__) && defined(__clang_major__)
 -    utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
 -                    CTIMEOPT_VAL(__clang_minor__) "."
 -                    CTIMEOPT_VAL(__clang_patchlevel__) "\n");
 -#elif defined(_MSC_VER)
 -    utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
 -#elif defined(__GNUC__) && defined(__VERSION__)
 -    utf8_printf(p->out, "gcc-" __VERSION__ "\n");
 +/* Argument-check and execute a found DotCommand, wrapping execution
 + * with command_{prep,post}(...), and issue errors as made evident.
 + * Return one of the "Post-execute action and success/error status"
 + * codes from the DotCmdRC enum.
 + *
 + * Note that this function is exposed for shell extensions to use.
 + *
 + * This should be called for "top-level" dot command execution only.
 + * Should an extension wrap or use a DotCommand object to effect its
 + * own functionality, that object's execute() method should be called
 + * directly, without going through this function.
 + */
 +static DotCmdRC runDotCommand(DotCommand *pmc, char *azArg[], int nArg,
 +                               ShellExState *psx){
 +  char *zErr = 0;
 +  DotCmdRC dcr = pmc->pMethods->argsCheck(pmc, &zErr, nArg, azArg);
 +
 +  command_prep(ISS(psx));
 +  if( dcr==DCR_Ok ){
 +    dcr = pmc->pMethods->execute(pmc, psx, &zErr, nArg, azArg);
 +  }
 +  if( dcr!=DCR_Ok ){
 +    dcr = dot_command_errors(zErr, azArg, nArg, dcr, psx);
 +  }
 +  sqlite3_free(zErr);
 +  command_post(ISS(psx));
 +  return dcr;
 +}
 +
 +/*
 +** If an input line or line group begins with "." then invoke this routine
 +** to process that line.
 +**
 +** Returns sanitized DotCmdRC values, with invocation error codes
 +** translated to DCR_Error, so that only these 8 returns are possible:
 +** DCR_Ok, DCR_Return, DCR_Exit or DCR_Abort possibly or'ed with DCR_Error.
 +**
 +** Any applicable error messages are issued along with output messages.
 +*/
 +static DotCmdRC do_dot_command(char *zLine, ShellExState *psx){
 +  int h = 1; /* Passing over leading '.' */
 +  int nArg = 0;
 +  char *azArg[52];
 +  DotCmdRC dcr = DCR_Ok;
 +#if SHELL_VARIABLE_EXPANSION
 +  int ncLineIn = strlen30(zLine);
 +  u8 bExpVars = SHEXT_VAREXP(ISS(psx));
  #endif
 -  }else
  
 -  if( c=='v' && cli_strncmp(azArg[0], "vfsinfo", n)==0 ){
 -    const char *zDbName = nArg==2 ? azArg[1] : "main";
 -    sqlite3_vfs *pVfs = 0;
 -    if( p->db ){
 -      sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
 -      if( pVfs ){
 -        utf8_printf(p->out, "vfs.zName      = \"%s\"\n", pVfs->zName);
 -        raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 -        raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 -        raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 +  /* Parse the input line into tokens which are 0-terminated and left in-place.
 +  */
 +  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
 +    /* Future: Complain if this fixed argument count limit is hit. */
 +    while( IsSpace(zLine[h]) ){ h++; }
 +    if( zLine[h]==0 ) break;
 +    if( zLine[h]=='\'' || zLine[h]=='"' ){
 +      int delim = zLine[h++];
 +      azArg[nArg++] = &zLine[h];
 +      while( zLine[h] && zLine[h]!=delim ){
 +        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
 +        h++;
        }
 -    }
 -  }else
 -
 -  if( c=='v' && cli_strncmp(azArg[0], "vfslist", n)==0 ){
 -    sqlite3_vfs *pVfs;
 -    sqlite3_vfs *pCurrent = 0;
 -    if( p->db ){
 -      sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
 -    }
 -    for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
 -      utf8_printf(p->out, "vfs.zName      = \"%s\"%s\n", pVfs->zName,
 -           pVfs==pCurrent ? "  <--- CURRENT" : "");
 -      raw_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
 -      raw_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
 -      raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
 -      if( pVfs->pNext ){
 -        raw_printf(p->out, "-----------------------------------\n");
 +      if( zLine[h]==delim ){
 +        zLine[h++] = 0;
        }
 +      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
 +    }else{
 +      azArg[nArg++] = &zLine[h];
 +      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
 +      if( zLine[h] ) zLine[h++] = 0;
 +      resolve_backslashes(azArg[nArg-1]);
      }
 -  }else
 +  }
 +  azArg[nArg] = 0; /* No code here relies on this, but some extension might. */
  
 -  if( c=='v' && cli_strncmp(azArg[0], "vfsname", n)==0 ){
 -    const char *zDbName = nArg==2 ? azArg[1] : "main";
 -    char *zVfsName = 0;
 -    if( p->db ){
 -      sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
 -      if( zVfsName ){
 -        utf8_printf(p->out, "%s\n", zVfsName);
 -        sqlite3_free(zVfsName);
 +  /* Process the input line. If it was empty, do nothing and declare success.
 +   * Note that "empty" includes a leading '.' followed by nothing else.
 +   */
 +  if( nArg>0 ){
 +    int nFound;
 +    DotCommand *pmc = findDotCommand(azArg[0], psx, &nFound);
 +    if( pmc==0 || nFound>1 ){
 +      if( nFound==0 ){
 +        dcr = DCR_Unknown;
 +#if SHELL_DYNAMIC_EXTENSION
 +        pmc = ISS(psx)->pUnknown;
 +        if( pmc ) dcr = runDotCommand(pmc, azArg, nArg, psx);
 +#endif
 +      }else{
 +        dcr = DCR_Ambiguous;
 +      }
 +      if( dcr > DCR_ArgIxMask ){
 +        /* Issue error for unknown or inadequately specified dot command. */
 +        dcr = dot_command_errors(0, azArg, nArg, dcr, psx);
        }
      }
 -  }else
 -
 -  if( c=='w' && cli_strncmp(azArg[0], "wheretrace", n)==0 ){
 -    unsigned int x = nArg>=2? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
 -    sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x);
 -  }else
 -
 -  if( c=='w' && cli_strncmp(azArg[0], "width", n)==0 ){
 -    int j;
 -    assert( nArg<=ArraySize(azArg) );
 -    p->nWidth = nArg-1;
 -    p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2);
 -    if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
 -    if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
 -    for(j=1; j<nArg; j++){
 -      p->colWidth[j-1] = (int)integerValue(azArg[j]);
 +    else{
 +      char *arg0 = azArg[0];
 +      azArg[0] = (char *)(pmc->pMethods->name(pmc));
 +      /* Run found command and issue or handle any errors it may report. */
 +      dcr = runDotCommand(pmc, azArg, nArg, psx);
 +      azArg[0] = arg0;
      }
 -  }else
 -
 -  {
 -    utf8_printf(stderr, "Error: unknown command or invalid arguments: "
 -      " \"%s\". Enter \".help\" for help\n", azArg[0]);
 -    rc = 1;
    }
  
 -meta_command_exit:
 -  if( p->outCount ){
 -    p->outCount--;
 -    if( p->outCount==0 ) output_reset(p);
 +#if SHELL_VARIABLE_EXPANSION
 +  if( bExpVars ){
 +    /* Free any arguments that are allocated rather than tokenized in place. */
 +    for( n=1; n<nArg; ++n ){
 +      int iArgOffset = azArg[n]-zLine;
 +      u8 bInPlace = iArgOffset>0 && iArgOffset<ncLineIn;
 +      if( !bInPlace ) sqlite3_free(azArg[n]);
 +    }
    }
 -  p->bSafeMode = p->bSafeModePersist;
 -  return rc;
 +#endif
 +  return dcr;
  }
  
  /* Line scan result and intermediate states (supporting scan resumption)
@@@ -15156,21 -11335,20 +15321,21 @@@ static void sql_prescan(const char *zLi
            goto TermScan;
          }
          break;
 +
        case '[':
          cin = ']';
-         /* fall thru */
+         deliberate_fall_through;
        case '`': case '\'': case '"':
          cWait = cin;
 -        qss = QSS_HasDark | cWait;
 -        CONTINUE_PROMPT_AWAITC(pst, cin);
 +        sss = SSS_HasDark | cWait;
 +      CONTINUE_PROMPT_AWAITC(pst, cin);
          goto TermScan;
        case '(':
 -        CONTINUE_PAREN_INCR(pst, 1);
 -        break;
 +      CONTINUE_PAREN_INCR(pst, 1);
 +      break;
        case ')':
 -        CONTINUE_PAREN_INCR(pst, -1);
 -        break;
 +      CONTINUE_PAREN_INCR(pst, -1);
 +      break;
        default:
          break;
        }
              ++zLine;
              continue;
            }
-           /* fall thru */
+           deliberate_fall_through;
          case ']':
            cWait = 0;
 -          CONTINUE_PROMPT_AWAITC(pst, 0);
 -          qss = QSS_SETV(qss, 0);
 +        CONTINUE_PROMPT_AWAITC(pst, 0);
 +          sss = SSS_SETV(sss, 0);
            goto PlainScan;
          default: assert(0);
          }
@@@ -15952,7 -11783,8 +16117,8 @@@ static void process_sqliterc
  /*
  ** Show available command line options
  */
 -static const char zOptions[] =
 +static const char *zOptions =
+   "   --                   treat no subsequent arguments as options\n"
  #if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE)
    "   -A ARGS...           run \".archive ARGS\" and exit\n"
  #endif
  #endif
  ;
  static void usage(int showDetail){
 -  utf8_printf(stderr,
 +  utf8_printf(STD_ERR,
-       "Usage: %s [OPTIONS] FILENAME [SQL]\n"
+       "Usage: %s [OPTIONS] [FILENAME [SQL]]\n"
        "FILENAME is the name of an SQLite database. A new database is created\n"
-       "if the file does not previously exist.\n", Argv0);
+       "if the file does not previously exist. Defaults to :memory:.\n", Argv0);
    if( showDetail ){
 -    utf8_printf(stderr, "OPTIONS include:\n%s", zOptions);
 +    utf8_printf(STD_ERR, "OPTIONS include:\n%s", zOptions);
    }else{
 -    raw_printf(stderr, "Use the -help option for additional information\n");
 +    raw_printf(STD_ERR, "Use the -help option for additional information\n");
    }
    exit(1);
  }
@@@ -16041,29 -11874,22 +16211,32 @@@ static void verify_uninitialized(void)
  }
  
  /*
 -** Initialize the state information in data
 +** Initialize the state information in data and datax
  */
 -static void main_init(ShellState *data) {
 -  memset(data, 0, sizeof(*data));
 -  data->normalMode = data->cMode = data->mode = MODE_List;
 -  data->autoExplain = 1;
 -  data->pAuxDb = &data->aAuxDb[0];
 -  memcpy(data->colSeparator,SEP_Column, 2);
 -  memcpy(data->rowSeparator,SEP_Row, 2);
 -  data->showHeader = 0;
 -  data->shellFlgs = SHFLG_Lookaside;
 -  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data);
 +  static void main_init(ShellInState *pData, ShellExState *pDatax) {
 +  memset(pData, 0, sizeof(*pData));
 +  memset(pDatax, 0, sizeof(*pDatax));
 +  pDatax->sizeofThis = sizeof(*pDatax);
 +  pDatax->pSIS = pData;
 +  pData->pSXS = pDatax;
 +  pDatax->pShowHeader = &pData->showHeader;
 +  pDatax->zFieldSeparator = &pData->colSeparator[0];
 +  pDatax->zRecordSeparator = &pData->rowSeparator[0];
 +  pDatax->zNullValue = &pData->nullValue[0];
 +  pData->out = STD_OUT;
 +  pData->normalMode = pData->cMode = pData->mode = MODE_List;
 +  pData->autoExplain = 1;
 +  pData->pAuxDb = &pData->aAuxDb[0];
 +  memcpy(pData->colSeparator,SEP_Column, 2);
 +  memcpy(pData->rowSeparator,SEP_Row, 2);
 +  pData->showHeader = 0;
 +  pData->shellFlgs = SHFLG_Lookaside;
++  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pData);
+ #if !defined(SQLITE_SHELL_FIDDLE)
    verify_uninitialized();
+ #endif
    sqlite3_config(SQLITE_CONFIG_URI, 1);
 +  sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pData);
    sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
    sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
    sqlite3_snprintf(sizeof(continuePrompt), continuePrompt,"   ...> ");
@@@ -16135,24 -11956,17 +16312,24 @@@ int SQLITE_CDECL SHELL_MAIN(int argc, w
    char **argv;
  #endif
  #ifdef SQLITE_DEBUG
-   sqlite3_int64 mem_main_enter = sqlite3_memory_used();
+   sqlite3_int64 mem_main_enter = 0;
  #endif
 -  char *zErrMsg = 0;
  #ifdef SQLITE_SHELL_FIDDLE
  #  define data shellState
 +#  define datax shellStateX
  #else
 -  ShellState data;
 +  ShellInState data;
 +  ShellExState datax;
 +#endif
 +#if SHELL_DATAIO_EXT
 +  BuiltInFFExporter ffExporter = BI_FF_EXPORTER_INIT( &data );
 +  BuiltInCMExporter cmExporter = BI_CM_EXPORTER_INIT( &data );
  #endif
    const char *zInitFile = 0;
 -  int i;
 +  int bQuiet = 0; /* for testing, to suppress banner and history actions */
 +  int i, aec;
    int rc = 0;
 +  DotCmdRC drc = DCR_Ok;
    int warnInmemoryDb = 0;
    int readStdin = 1;
    int nCmd = 0;
    char **argvToFree = 0;
    int argcToFree = 0;
  #endif
-   setBinaryMode(STD_IN, 0);
 -  setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
 +  setvbuf(STD_ERR, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
  #ifdef SQLITE_SHELL_FIDDLE
    stdin_is_interactive = 0;
    stdout_is_console = 1;
    stdin_is_interactive = isatty(0);
    stdout_is_console = isatty(1);
  #endif
+ #if SHELL_WIN_UTF8_OPT
+   atexit(console_restore); /* Needs revision for CLI as library call */
+ #endif
+   atexit(sayAbnormalExit);
+ #ifdef SQLITE_DEBUG
+   mem_main_enter = sqlite3_memory_used();
+ #endif
 +
  #if !defined(_WIN32_WCE)
    if( getenv("SQLITE_DEBUG_BREAK") ){
      if( isatty(0) && isatty(2) ){
    ** the size of the alternative malloc heap,
    ** and the first command to execute.
    */
+ #ifndef SQLITE_SHELL_FIDDLE
    verify_uninitialized();
 -  for(i=1; i<argc; i++){
+ #endif
 +  for(i=1; i<argc && rc<2; i++){
      char *z;
      z = argv[i];
-     if( z[0]!='-' ){
+     if( z[0]!='-' || i>nOptsEnd ){
        if( data.aAuxDb->zDbFilename==0 ){
          data.aAuxDb->zDbFilename = z;
        }else{
  #endif
      }else if( cli_strcmp(z,"-mmap")==0 ){
        sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
+       verify_uninitialized();
        sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz);
 -#if defined(SQLITE_ENABLE_SORTER_REFERENCES)
 +#ifdef SQLITE_ENABLE_SORTER_REFERENCES
      }else if( cli_strcmp(z,"-sorterref")==0 ){
        sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
+       verify_uninitialized();
        sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz);
  #endif
      }else if( cli_strcmp(z,"-vfs")==0 ){
      }else if( cli_strcmp(z,"-nonce")==0 ){
        free(data.zNonce);
        data.zNonce = strdup(argv[++i]);
 +    }else if( cli_strcmp(z,"-quiet")==0 ){
 +      bQuiet = (int)integerValue(cmdline_option_value(argc,argv,++i));
+     }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
+       ShellSetFlag(&data,SHFLG_TestingMode);
      }else if( cli_strcmp(z,"-safe")==0 ){
 -      /* no-op - catch this on the second pass */
 +      /* catch this on the second pass (Unsafe is fine on invocation.) */
      }
    }
+ #ifndef SQLITE_SHELL_FIDDLE
    verify_uninitialized();
+ #endif
  
 -
  #ifdef SQLITE_SHELL_INIT_PROC
    {
      /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
    ** file is processed so that the command-line arguments will override
    ** settings in the initialization file.
    */
 -  for(i=1; i<argc; i++){
 +  for(i=1; i<argc && rc<2; i++){
      char *z = argv[i];
-     if( z[0]!='-' ) continue;
 +    char *zModeSet = 0;
+     if( z[0]!='-' || i>=nOptsEnd ) continue;
      if( z[1]=='-' ){ z++; }
      if( cli_strcmp(z,"-init")==0 ){
        i++;
        break;
  #endif
      }else if( cli_strcmp(z,"-safe")==0 ){
 -      data.bSafeMode = data.bSafeModePersist = 1;
 +      data.bSafeMode = data.bSafeModeFuture = 1;
+     }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
+       /* Acted upon in first pass. */
 +    }else if( cli_strcmp(z,"-quiet")==0 ){
 +      ++i;
      }else{
 -      utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
 -      raw_printf(stderr,"Use -help for a list of options.\n");
 -      return 1;
 +      utf8_printf(STD_ERR,"%s: Error: unknown option: %s\n", Argv0, z);
 +      raw_printf(STD_ERR,"Use -help for a list of options.\n");
 +      rc = 2;
 +    }
 +    if( zModeSet!=0 ){
 +      char *azModeCmd[] = { ".mode", zModeSet+1 };
 +      modeCommand(azModeCmd, 2, &datax, 0);
 +      data.cMode = data.mode;
      }
 -    data.cMode = data.mode;
    }
+ #if SHELL_WIN_UTF8_OPT
+   if( console_utf8 && stdin_is_interactive ){
+     console_prepare();
+   }else{
+     setBinaryMode(stdin, 0);
+     console_utf8 = 0;
+   }
+ #endif
  
    if( !readStdin ){
      /* Run all arguments that do not begin with '-' as if they were separate
Simple merge
Simple merge
Simple merge
index 1db38c4c5bc254cc7d495597c5f2a46154217ccb,3fad4bd665613d23cebdc48fea9d61376ae6f09d..81c78e7f7c204f7aeb0c2fae5e5759eb3cf0b71f
  do_test shell2-1.4.7 {
    catchcmd ":memory:" {
   SELECT 'unclosed;}
 -} {1 {Parse error near line 2: unrecognized token: "'unclosed;"
 -  SELECT 'unclosed;
 -         ^--- error here}}
 +} {1 {Error: Input incomplete at line 2 of "<stdin>"}}
  
+ # Verify that safe mode rejects certain UDFs
+ # Reported at https://sqlite.org/forum/forumpost/07beac8056151b2f
+ do_test shell2-1.4.8 {
+   catchcmd "-safe :memory:" {
+  SELECT edit('DoNotCare');}
+ } {1 {line 2: cannot use the edit() function in safe mode}}
+ do_test shell2-1.4.9 {
+   catchcmd "-safe :memory:" {
+  SELECT writefile('DoNotCare', x'');}
+ } {1 {line 2: cannot use the writefile() function in safe mode}}
+ # Verify that .clone handles sequence table.
+ # See https://sqlite.org/forum/forumpost/71ff9e6c4c
+ do_test shell2-1.4.9 {
+   forcedelete clone.db
+   set res [catchcmd :memory: [string trim {
+  CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT);
+  INSERT INTO t VALUES (1),(2);
+ .clone clone.db
+ .open clone.db
+  SELECT max(seq) FROM sqlite_sequence;}]]
+ } {0 {t... done
+ done
+ 2}}
+ # Verify that generate_series stays sane near 64-bit range boundaries.
+ # See overflow report at https://sqlite.org/forum/forumpost/5d34ce5280
+ do_test shell2-1.4.10 {
+  set res [catchcmd :memory: [string trim {
+  SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1);
+  SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1);
+  SELECT avg(rowid),min(value),max(value) FROM generate_series(
+   -9223372036854775808,9223372036854775807,1085102592571150095);
+  SELECT * FROM generate_series(-9223372036854775808,9223372036854775807,
+   9223372036854775807);
+  SELECT value,rowid FROM generate_series(-4611686018427387904,
+   4611686018427387904, 4611686018427387904) ORDER BY value DESC;
+  SELECT * FROM generate_series(0,-2,-1);
+  SELECT * FROM generate_series(0,-2);
+  SELECT * FROM generate_series(0,2) LIMIT 3;}]]
+ } {0 {9223372036854775807
+ 9223372036854775807
+ 9.5|-9223372036854775808|9223372036854775807
+ -9223372036854775808
+ -1
+ 9223372036854775806
+ 4611686018427387904|3
+ 0|2
+ -4611686018427387904|1
+ 0
+ -1
+ -2
+ 0
+ 1
+ 2}}
+ # Bug discovered while messing around, .import hangs with
+ # bit 7 set in column separator.
+ do_test shell2-1.4.11 {
+   forcedelete dummy.csv
+   set df [open dummy.csv w]
+   puts $df dog,cat
+   close $df
+   set res [catchcmd :memory: [string trim {
+  CREATE TABLE t(line text);
+ .mode ascii
+ .separator "\377" "\n"
+ .import dummy.csv t
+  SELECT count(*) FROM t;}]]
+ } {0 1}
  finish_test
Simple merge
Simple merge
Simple merge
diff --cc test/tester.tcl
index f0890641c76bf2c8072a072def486087a2ad0f63,021830aa95c70878f1baf9aed359696fbba11434..c90ca2fbd7dc3415ddb36d7daa862537e3e9da12
@@@ -2451,8 -2528,10 +2527,10 @@@ proc test_restore_config_pagecache {} 
    catch {db3 close}
  
    sqlite3_shutdown
-   eval sqlite3_config_pagecache $::old_pagecache_config
-   unset ::old_pagecache_config
+   if {[info exists ::old_pagecache_config]} {
+     eval sqlite3_config_pagecache $::old_pagecache_config
 -    unset ::old_pagecache_config 
++    unset ::old_pagecache_config
+   }
    sqlite3_initialize
    autoinstall_test_functions
    sqlite3 db test.db
@@@ -2477,11 -2556,11 +2555,11 @@@ proc test_find_binary {nm} 
  }
  
  # Find the name of the 'shell' executable (e.g. "sqlite3.exe") to use for
- # the tests in shell[1-5].test. If no such executable can be found, invoke
+ # the tests in shell*.test. If no such executable can be found, invoke
  # [finish_test ; return] in the callers context.
  #
 -proc test_find_cli {} {
 -  set prog [test_find_binary sqlite3]
 +proc test_find_cli {{cli_name {sqlite3}}} {
 +  set prog [test_find_binary $cli_name]
    if {$prog==""} { return -code return }
    return $prog
  }
index f09e92f4dc5d2a0a02074d3fd2d8402588469fee,d37b2e48c6e2f95de2bcaf0c6302d4c5cf89d69a..0cd54994f7405c8a51d5734ca7bc3ce51240435a
@@@ -8,8 -8,8 +8,9 @@@ source [file join [file dirname [info s
  # Variables set by the "control" form:
  #
  #   G(platform) - User selected platform.
 -#   G(test)     - Set to "Normal", "Veryquick", "Smoketest" or "Build-Only".
 +#   G(test)     - Set to "Normal", "Veryquick", "Smoketest", "ShellExt"
 +#                 or "Build-Only".
+ #   G(cfgglob)  - Glob pattern that all configurations must match
  #   G(keep)     - Boolean. True to delete no files after each test.
  #   G(msvc)     - Boolean. True to use MSVC as the compiler.
  #   G(tcl)      - Use Tcl from this directory for builds.