From: larrybr Date: Mon, 8 May 2023 21:35:07 +0000 (+0000) Subject: WIP, sync with trunk to pickup 22 weeks of shell library enhancements. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d6a1c2e0a2a295b13a0f3a5cd387a7bf030e6a92;p=thirdparty%2Fsqlite.git WIP, sync with trunk to pickup 22 weeks of shell library enhancements. FossilOrigin-Name: 00d0d5b10556bcc4ffb8d6083669134a913bc6ae7fcc787af5b1c292923354e8 --- d6a1c2e0a2a295b13a0f3a5cd387a7bf030e6a92 diff --cc Makefile.in index 8e095174a6,61cb3ef0e1..1a1e18ace8 --- a/Makefile.in +++ b/Makefile.in @@@ -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 manifest index 09b0d04923,02343baa6b..c06480f18c --- a/manifest +++ 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/obj_interfaces.h c80525ea603357a79ddae45ba98eec2ac60a9092d1800811d92f3a0e0208d8a6 - 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/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 d00932098a,16a0d0a53a..150331299d --- a/manifest.uuid +++ b/manifest.uuid @@@ -1,1 -1,1 +1,1 @@@ - 3db119c8d754979ceb16253f1b79b645a5bc68b399406cacc4c50a2a71e84e2d -4ffae48e831eedf8f5e61dc6d38817c0fdccfb2f4f1189d07f9722b9e3a48b5e ++00d0d5b10556bcc4ffb8d6083669134a913bc6ae7fcc787af5b1c292923354e8 diff --cc src/shell.c.in index 1e1ca18382,567752e9b0..bcdbdc3e0b --- a/src/shell.c.in +++ b/src/shell.c.in @@@ -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 " */ +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 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; @@@ -924,81 -978,41 +1096,92 @@@ ** 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 ++ return zResult; + } +} +#else /* !defined(SQLITE_SHELL_FIDDLE) */ +/* - ** 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). ++** 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 +/* +** 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; - /* Fall thru, suspension is in effect. */ ++ 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 ){ @@@ -3130,27 -2732,27 +3322,27 @@@ 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( iout); + 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); @@@ -4087,12 -3566,20 +4287,20 @@@ 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; inFilter; 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; jnSession; 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; inFilter; 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*) 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(, '\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 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=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; itraceOut==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; i1000000000 ) 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; itraceOut,"%.*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; inFilter; 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; jnSession; 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; inFilter; 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; jaAuxDb[].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; idb, "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 ", where +** 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=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]; pOptcShort ) 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; iArgazArg = &azArg[iArg]; + pAr->nArg = nArg-iArg; + break; + } + n = strlen30(z); + + if( z[1]!='-' ){ + int i; + /* One or more short options */ + for(i=1; icShort ) 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]; pOptzLong; + 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; inArg && 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; iout, 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; iout, "%-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; jout, "\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; idb, 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)\ + 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; ipData); + 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 ", where -** 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 && mipsi); +} +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; i1 && 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; ipSpecWidths[i] = 0; + psx->numWidths = nColumn; + psx->pHaveWidths = &psx->pSpecWidths[nColumn]; + } + memset(psx->pHaveWidths, 0, nColumn*sizeof(int)); + for(i=0; ipSpecWidths[i]; + if( w<0 ) w = -w; + psx->pHaveWidths[i] = w; + } + for(i=0; ipSpecWidths[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; ipSpecWidths[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; ipsx->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; ipHaveWidths[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; iout, 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; ipHaveWidths[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; ipHaveWidths[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; ipHaveWidths[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; icMode!=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+1cMode==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; icolInfo.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; iaiTypes[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; inArg; 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), {, }, 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; iaout, "%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; iaAuxDb); 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 && iaAuxDb) ){ + 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; iout, "%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; ii1 && 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; idb, "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=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; inArg && 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; iout, "-- 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+1outCount = 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; iout, "Available file-controls:\n"); + for(i=0; iout, " .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; iout, 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; icolSeparator); + 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 = ""; + 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=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; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; + sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); + 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)\ - 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( i75){ + 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; i3 ){ + return DCR_TooMany; + }else{ + int iLimit = -1; + n2 = strlen30(azArg[1]); + for(i=0; iout; /* 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; i1 && 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( aiexpert.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] && nArgshellFlgs & 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
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; aixout; + const char *zMode; + int nms; + i = psi->mode; + assert(i>=0 && imode) ){ + 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=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; jpAuxDb->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( azBeg2 || 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; iaAuxDb); 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 && iaAuxDb) ){ - 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; idb, 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; ii1 && 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; iout, "%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; iflgProgress |= 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; ibSafeMode ){ - 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; i0" + " 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; iout, " .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; i1" + , &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; iout, "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 && iout, "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 = ""; - 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=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=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; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; - sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); - 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; idb, 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; idb, 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; iSesnSession; iSes++){ + if( cli_strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break; + } + if( iSesnSession ){ + 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 @@@ -12712,2394 -9495,1793 +12865,2406 @@@ } }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; iout, "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+1out,"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; inSession; 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; inSession; 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; ioutCount = 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; iout, "%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; iout, "%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; idb, 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; i1 ) 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; iflgProgress |= 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= 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=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<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<1 ){ + for( ia=1; iabExtendedDotCmds |= 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; iobExtendedDotCmds & 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;inumWidths;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; imaxlen ) 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; iout, "%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; ii1 && 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; iidb, "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; idb, 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; iSesnSession; 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( iSesnSession ){ - 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; iinFilter; 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; iiazFilter[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; inSession; 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; inSession; 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; jjeTraceType = 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; iout, "%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; iout, "%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=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; iidb,"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; iout, 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; iout, "%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;inWidth;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; imaxlen ) maxlen = len; - } - nPrintCol = 80/(maxlen+2); - if( nPrintCol<1 ) nPrintCol = 1; - nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; - for(i=0; iout, "%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; iiout = 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; jpSpecWidths[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( rvDCR_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; iout, " .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 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; ibSafeMode ){ - utf8_printf(stderr, - "line %d: \".testctrl %s\" may not be used in safe mode\n", - p->lineno, aCtrl[iCtrl].zCtrlName); - exit(1); + if( rvDCR_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= +COMMENT where 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( pCIcmdName)==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(extIxnumExtLoaded && 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( pCIzPattern, 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; jjeTraceType = 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)) + || + (ixm1 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; iidb, 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( ixctazHelp[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] && nArgdb ){ - 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; jcolWidth[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; n0 && iArgOffsetbSafeMode = 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; } @@@ -15195,11 -11373,11 +15360,11 @@@ ++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 @@@ -16017,14 -11850,14 +16187,14 @@@ #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; @@@ -16162,8 -11977,8 +16340,8 @@@ 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; @@@ -16172,7 -11987,13 +16350,14 @@@ 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) ){ @@@ -16258,11 -12078,13 +16451,13 @@@ ** the size of the alternative malloc heap, ** and the first command to execute. */ + #ifndef SQLITE_SHELL_FIDDLE verify_uninitialized(); + #endif - for(i=1; inOptsEnd ){ if( data.aAuxDb->zDbFilename==0 ){ data.aAuxDb->zDbFilename = z; }else{ @@@ -16348,10 -12178,12 +16551,12 @@@ #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 ){ @@@ -16389,14 -12217,17 +16594,18 @@@ }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 @@@ -16468,10 -12287,9 +16677,10 @@@ ** file is processed so that the command-line arguments will override ** settings in the initialization file. */ - for(i=1; i=nOptsEnd ) continue; if( z[1]=='-' ){ z++; } if( cli_strcmp(z,"-init")==0 ){ i++; @@@ -16627,20 -12454,24 +16840,30 @@@ 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 diff --cc test/shell2.test index 1db38c4c5b,3fad4bd665..81c78e7f7c --- a/test/shell2.test +++ b/test/shell2.test @@@ -191,6 -187,80 +185,78 @@@ 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 ""}} + # 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 diff --cc test/tester.tcl index f0890641c7,021830aa95..c90ca2fbd7 --- a/test/tester.tcl +++ b/test/tester.tcl @@@ -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 } diff --cc test/wapptest.tcl index f09e92f4dc,d37b2e48c6..0cd54994f7 --- a/test/wapptest.tcl +++ b/test/wapptest.tcl @@@ -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", "ShellExt" +# or "Build-Only". + # G(cfgglob) - Glob pattern that all configurations must match -# G(test) - Set to "Normal", "Veryquick", "Smoketest" or "Build-Only". # 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.