Compare commits
354 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 84c1079220 | |||
| c8d866294b | |||
| 9efde40a5d | |||
| 09f37099a7 | |||
| 526acc1834 | |||
| d962afef2d | |||
| 395ac6c1bc | |||
| e9d0bc6b68 | |||
| ab6a63ec67 | |||
| 57ed7f322f | |||
| 34c2c8dc47 | |||
| 3f9df1f5fb | |||
| 2eeac123c8 | |||
| 64a123a216 | |||
| a10e79d91a | |||
| d20673a95f | |||
| f02d100e08 | |||
| 4c8f0621fd | |||
| 6abc21cfb8 | |||
| 20079bb358 | |||
| 3d6fa49c54 | |||
| 4df365e5a7 | |||
| 9d8bf6f04a | |||
| c0026ff7a6 | |||
| 1e3f888acf | |||
| 2d4dfbccdc | |||
| 6142310e08 | |||
| 42ae7e7f34 | |||
| 57cf5c2dfc | |||
| a212476ad2 | |||
| d08caad3a5 | |||
| 28456958ca | |||
| 3b27ffa534 | |||
| fc2c730c87 | |||
| cd28797117 | |||
| c8b130f255 | |||
| 7a0a0a22e7 | |||
| 504cfd5ec7 | |||
| 9d950aa13b | |||
| b709ba8108 | |||
| 01e1be854b | |||
| 70868a5ae3 | |||
| d0065c8118 | |||
| bd574dbaee | |||
| 7632922625 | |||
| 49998a6d25 | |||
| 92c435cda7 | |||
| 35f8abcfd0 | |||
| 1a315c0d51 | |||
| 3a8d38ffc0 | |||
| 693b15ac83 | |||
| 8cdc2abf07 | |||
| 931fa90d74 | |||
| eb9a6a95ca | |||
| b37bf0b8b4 | |||
| 850104bbad | |||
| 6a6ebb0d06 | |||
| 30b824e432 | |||
| 3944e70677 | |||
| df0e915ad2 | |||
| b0d9039c8c | |||
| f1cc3c4be5 | |||
| 3aed7dc446 | |||
| 9a8732e44b | |||
| 4ebc7fdcf4 | |||
| a5495355c4 | |||
| df3e207501 | |||
| 535b1f2875 | |||
| a7200804eb | |||
| 0d979c217f | |||
| e9af8caac0 | |||
| 606c64a543 | |||
| eefe3aa9ad | |||
| fcef193a82 | |||
| 8e7ffc09f5 | |||
| 9ad63c0f4e | |||
| d4b74b9fac | |||
| 7ff8b558e8 | |||
| bb6d2a5f07 | |||
| 217c590688 | |||
| fc19b2ca8a | |||
| 939930db1e | |||
| 62f3150204 | |||
| 34e139d3a3 | |||
| 6e6598c8ff | |||
| e230de2cbf | |||
| eb8edc0bac | |||
| d6226a9c27 | |||
| d9f047bd15 | |||
| 5e4fb95edf | |||
| 60b829e0db | |||
| f92d107242 | |||
| b060cecb2e | |||
| 9660a4dec0 | |||
| 0cb576f306 | |||
| 6d27844bbe | |||
| 03f13d77a5 | |||
| 445fbc61c8 | |||
| eb0cb3f53b | |||
| 5239e69fb1 | |||
| 20d93e16d7 | |||
| c409eaaf14 | |||
| 2e01416762 | |||
| ff8a0895d6 | |||
| a2f78f665c | |||
| c4cedd7ffe | |||
| 00c6eca32c | |||
| be2242d2ee | |||
| 2dd3b4a646 | |||
| 4570aeb7bc | |||
| 1283969f26 | |||
| 772ea2f01c | |||
| d4af925e34 | |||
| 61d3bc8ddc | |||
| f9a3b78561 | |||
| f855727e45 | |||
| 3d58f022a8 | |||
| 7e9eaf3c1f | |||
| 0944b3efad | |||
| f3f194c385 | |||
| 183a73c352 | |||
| 53197b7940 | |||
| f1f1784f4b | |||
| 814f592cf0 | |||
| 9b805c6248 | |||
| 3c2ccd2903 | |||
| 7bf4d708dc | |||
| 5239e821e8 | |||
| a217a4af09 | |||
| 3e3c31ff0e | |||
| dc32016ee0 | |||
| 5700f8c471 | |||
| 80b88b2482 | |||
| 8e7f122c7d | |||
| dfd31e158c | |||
| cc19c1dafd | |||
| 99571bf3c3 | |||
| 3c1f727603 | |||
| d9e70f2b90 | |||
| 4c78799bb0 | |||
| 958b6a52e5 | |||
| f62cff6a9e | |||
| f163bfba4e | |||
| b0a4b69b95 | |||
| bd580270a5 | |||
| de4ef2dc70 | |||
| 0cb7603d4c | |||
| 28cfb1e4a7 | |||
| 915a55444b | |||
| 58012b85b0 | |||
| a2d80200b1 | |||
| 08eb5e9e51 | |||
| b425ebdd4f | |||
| ac53ce155a | |||
| 5a6772eaa5 | |||
| acc213b42f | |||
| 345bc91673 | |||
| 1539575e05 | |||
| f0648f6924 | |||
| 15a3b2d4e1 | |||
| 765ff2189a | |||
| 3ca448dee5 | |||
| 3f2d7d4aaa | |||
| 00989bcd9f | |||
| 362b9cecdb | |||
| f4e6483cda | |||
| ae2820ab28 | |||
| bdbad43f33 | |||
| d6965b1dab | |||
| ccf2babd42 | |||
| 2953e950c8 | |||
| 8736da94bc | |||
| b302c4d39c | |||
| 022f2f96bc | |||
| 5a73386511 | |||
| 1c1f7c259e | |||
| cf93c51790 | |||
| b50ce08591 | |||
| e9018e5ff5 | |||
| 4cc023bd01 | |||
| f6308e68ff | |||
| 066a5bcdd7 | |||
| b2006499c7 | |||
| bf057073c3 | |||
| d21413adcb | |||
| 061459bf76 | |||
| a6640846dd | |||
| 7539d2f1ba | |||
| 0f08d9586c | |||
| 6c446015bd | |||
| 9e572064f3 | |||
| 169f68ed88 | |||
| c739d13217 | |||
| 3bd23b0d78 | |||
| abc82c49c2 | |||
| bd10a18f00 | |||
| efa8633fd8 | |||
| 140cbff0d2 | |||
| e795890d7e | |||
| 5ed6e4ccd1 | |||
| 016a0ebedc | |||
| 456c7ea715 | |||
| 5236bad923 | |||
| 46a692c08b | |||
| 02cbc96d09 | |||
| 1eb335e313 | |||
| 976b50620a | |||
| 303fad1f91 | |||
| 03b0f8fc9c | |||
| 84ad6f302f | |||
| 9358070cb3 | |||
| eac7cd634d | |||
| 13a494fddd | |||
| 85ee7e15e3 | |||
| ac26170191 | |||
| cde22e8806 | |||
| b4f257f5e0 | |||
| 237fce8205 | |||
| 78649bb421 | |||
| 7d10030d75 | |||
| f20c4e650d | |||
| a0851f1483 | |||
| 6142524b70 | |||
| 9be458e611 | |||
| 57d0fe254a | |||
| 3866d01907 | |||
| 02d9860c8e | |||
| 7f6236bed1 | |||
| 56b571780e | |||
| 84334c941a | |||
| ac63b524ac | |||
| 8f65da4574 | |||
| 2251f0c4cc | |||
| 1bf79ca445 | |||
| 3ed9043c71 | |||
| 7c483444a1 | |||
| 7cc8db2f5d | |||
| a85d09f1a4 | |||
| 275e7c7caa | |||
| 88e552f1b9 | |||
| a98f278f58 | |||
| c008b8fe3e | |||
| 7b9cd03c0a | |||
| 257fdf8496 | |||
| ad2fe531d0 | |||
| 93353c0bc2 | |||
| 544ef18bbd | |||
| ea595494e2 | |||
| 891f5a176b | |||
| 831e233202 | |||
| 7a125b97d7 | |||
| d61ecd83b8 | |||
| 21bdb75ac8 | |||
| 46686ac681 | |||
| 14b5eadefe | |||
| cef3ff74cd | |||
| bef2a1e33e | |||
| 671a6f83f4 | |||
| 232446b532 | |||
| b85067c568 | |||
| 080da5f56d | |||
| 691494a93b | |||
| 6ff9fcfad2 | |||
| 0f7f9ab007 | |||
| 76e562e222 | |||
| 96daad10fa | |||
| 6d6bf30735 | |||
| 9ac95d9ce1 | |||
| a2d3a54f80 | |||
| 0b7b802d83 | |||
| e2309eebd7 | |||
| 72a14a575e | |||
| d016fad5a8 | |||
| 8ba2a38363 | |||
| bb80d688ac | |||
| fe57e14b49 | |||
| 5c4692b6a9 | |||
| a2fef84b19 | |||
| 18f99f43bc | |||
| f6c8adf3a2 | |||
| 72afb50a6d | |||
| 7fa89ea907 | |||
| c1454ee833 | |||
| ea6b2a87d1 | |||
| f270460366 | |||
| 33478099c9 | |||
| 93ad2e083e | |||
| ffaf9110f6 | |||
| 7e4e866baf | |||
| e293b2d835 | |||
| 83de60bd7b | |||
| a000db9754 | |||
| 7ac310d0c6 | |||
| afaf7ea2e4 | |||
| f707e2ec5d | |||
| 7d9fbe6476 | |||
| c67a538168 | |||
| 57d7a20ce7 | |||
| d22d0edc48 | |||
| 8c6bf948b7 | |||
| 5dfccfb0bb | |||
| 5a6eef2fb2 | |||
| 8bc78bebfb | |||
| a86c6017e7 | |||
| 1a152ea92a | |||
| f45c695471 | |||
| 46bf39e396 | |||
| b1a73ba34d | |||
| 9d65290207 | |||
| da67225f04 | |||
| d2e27e5d54 | |||
| ffd9892a02 | |||
| 9235f9b049 | |||
| 9dac613f04 | |||
| 95fe5b9c38 | |||
| 9bc017423c | |||
| 60f132bf12 | |||
| f8681fb638 | |||
| 283f33233b | |||
| 8642ca2025 | |||
| 699959ff25 | |||
| 7aba858597 | |||
| 4428aa842c | |||
| 24ef9fbb19 | |||
| aec283385e | |||
| ac5ee556eb | |||
| 62546d4b1f | |||
| 9e395efec6 | |||
| d447df4205 | |||
| 20c65785cd | |||
| e8750019c2 | |||
| cac09127e1 | |||
| 37ee141d25 | |||
| 7549e784a8 | |||
| 8c3e8dce41 | |||
| 31d4e56b29 | |||
| 530f536ce7 | |||
| eb8c4c5742 | |||
| 0b5e944480 | |||
| 63338e3ba8 | |||
| 6315919ba8 | |||
| 5c977eaa0a | |||
| 1105c33900 | |||
| 7b93f1a387 | |||
| c47bc0ffca | |||
| 990d861038 | |||
| 23e57bc7cd | |||
| 7d49eb1dff | |||
| df59c5da78 | |||
| c1975b6a6f | |||
| de88171c4c | |||
| 613cf76c2f | |||
| c289cf57a7 | |||
| 817c509365 |
+11
-3
@@ -131,9 +131,15 @@ libsqlite3.DLL.install-rules = @SQLITE_DLL_INSTALL_RULES@
|
||||
CFLAGS.fuzzcheck-asan.fsanitize = @CFLAGS_ASAN_FSANITIZE@
|
||||
|
||||
#
|
||||
# Intended to either be empty or be set to -g -DSQLITE_DEBUG=1.
|
||||
# TARGET_DEBUG is intended to either be empty or be set to -g
|
||||
# -DSQLITE_DEBUG=1, plus any other flags relevant for debug-only
|
||||
# builds. These get added near the front of $(T.cc) so that they can
|
||||
# be overridden by CFLAGS passed directly to make. Historically (prior
|
||||
# to 2026-03-30) these were at/near the end of the flags but that made
|
||||
# it impossible to pass custom -O* flags to specific binaries without
|
||||
# reconfiguring.
|
||||
#
|
||||
T.cc.TARGET_DEBUG = @TARGET_DEBUG@
|
||||
T.cc += @TARGET_DEBUG@
|
||||
|
||||
#
|
||||
# $(JIMSH) and $(CFLAGS.jimsh) are documented in main.mk. $(JIMSH)
|
||||
@@ -164,7 +170,9 @@ OPT_FEATURE_FLAGS = @OPT_FEATURE_FLAGS@ $(OPTIONS)
|
||||
PACKAGE_VERSION = @PACKAGE_VERSION@
|
||||
|
||||
#
|
||||
# Filename extensions for binaries and libraries
|
||||
# Filename extensions for binaries and libraries. B.* are for the
|
||||
# being-built-on platform and T.* are for the build's target platform.
|
||||
# i.e. B.* is for build-time tooling and T.* is for deliverables.
|
||||
#
|
||||
B.exe = @BUILD_EXEEXT@
|
||||
T.exe = @TARGET_EXEEXT@
|
||||
|
||||
+35
-49
@@ -1719,6 +1719,7 @@ FUZZERSHELL_COMPILE_OPTS =
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -I$(TOP)\test -I$(TOP)\ext\recover
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_MEMSYS5
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_OSS_FUZZ
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_DECIMAL_MAX_DIGIT=1000
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_BYTECODE_VTAB
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_CARRAY
|
||||
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DBPAGE_VTAB
|
||||
@@ -1765,7 +1766,17 @@ FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\fuzzinvariants.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\test\vt02.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\dbdata.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\recover\sqlite3recover.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\base64.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\base85.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\completion.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\decimal.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\ieee754.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\randomjson.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\regexp.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\series.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\shathree.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\sha1.c
|
||||
FUZZCHECK_SRC = $(FUZZCHECK_SRC) $(TOP)\ext\misc\stmtrand.c
|
||||
|
||||
OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c
|
||||
DBFUZZ_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION
|
||||
@@ -2540,43 +2551,19 @@ coretestprogs: testfixture.exe sqlite3.exe
|
||||
|
||||
testprogs: $(TESTPROGS) srcck1.exe fuzzcheck.exe sessionfuzz.exe
|
||||
|
||||
fulltest: alltest fuzztest
|
||||
|
||||
alltest: $(TESTPROGS)
|
||||
@set PATH=$(LIBTCLPATH);$(PATH)
|
||||
.\testfixture.exe $(TOP)\test\all.test $(TESTOPTS)
|
||||
|
||||
soaktest: $(TESTPROGS)
|
||||
@set PATH=$(LIBTCLPATH);$(PATH)
|
||||
.\testfixture.exe $(TOP)\test\all.test -soak=1 $(TESTOPTS)
|
||||
|
||||
fulltestonly: $(TESTPROGS) fuzztest
|
||||
@set PATH=$(LIBTCLPATH);$(PATH)
|
||||
.\testfixture.exe $(TOP)\test\full.test
|
||||
|
||||
queryplantest: testfixture.exe shell
|
||||
@set PATH=$(LIBTCLPATH);$(PATH)
|
||||
.\testfixture.exe $(TOP)\test\permutations.test queryplanner $(TESTOPTS)
|
||||
|
||||
fuzztest: fuzzcheck.exe
|
||||
.\fuzzcheck.exe $(FUZZDATA)
|
||||
|
||||
# Legacy testing target for third-party integrators. The SQLite
|
||||
# developers seldom use this target themselves. Instead
|
||||
# they use "nmake /f Makefile.msc devtest" which runs tests on
|
||||
# a standard set of options
|
||||
#
|
||||
test: $(TESTPROGS) sourcetest fuzztest tcltest
|
||||
|
||||
# Minimal testing that runs in less than 3 minutes (on a fast machine)
|
||||
#
|
||||
quicktest: testfixture.exe sourcetest
|
||||
@set PATH=$(LIBTCLPATH);$(PATH)
|
||||
.\testfixture.exe $(TOP)\test\extraquick.test $(TESTOPTS)
|
||||
|
||||
# This is the common case. Run many tests that do not take too long,
|
||||
# including fuzzcheck, sqlite3_analyzer, and sqldiff tests.
|
||||
# Legacy testing targets, no longer used by the developers and
|
||||
# now aliased to one of the commonly used testing targets.
|
||||
#
|
||||
quicktest: devtest
|
||||
test: devtest
|
||||
fulltest: releasetest
|
||||
alltest: releasetest
|
||||
soaktest: releasetest
|
||||
fulltestonly: releasetest
|
||||
|
||||
# The veryquick.test TCL tests.
|
||||
#
|
||||
@@ -2640,6 +2627,12 @@ smoketest: $(TESTPROGS)
|
||||
@set PATH=$(LIBTCLPATH);$(PATH)
|
||||
.\testfixture.exe $(TOP)\test\main.test $(TESTOPTS)
|
||||
|
||||
# Measure the performance of floating-point conversions.
|
||||
#
|
||||
fp-speed-test: fp-speed-1.exe fp-speed-2.exe
|
||||
fp-speed-1 1000000
|
||||
fp-speed-2 1000000
|
||||
|
||||
shelltest:
|
||||
$(TCLSH_CMD) $(TOP)\test\testrunner.tcl release shell
|
||||
|
||||
@@ -2660,23 +2653,6 @@ sqltclsh.exe: sqltclsh.c $(SHELL_CORE_DEP) $(LIBRESOBJS)
|
||||
sqlite3_expert.exe: $(SQLITE3C) $(TOP)\ext\expert\sqlite3expert.h $(TOP)\ext\expert\sqlite3expert.c $(TOP)\ext\expert\expert.c
|
||||
$(LTLINK) $(NO_WARN) $(TOP)\ext\expert\sqlite3expert.c $(TOP)\ext\expert\expert.c $(SQLITE3C) $(TLIBS)
|
||||
|
||||
CHECKER_DEPS =\
|
||||
$(TOP)\tool\mkccode.tcl \
|
||||
sqlite3.c \
|
||||
tclsqlite-ex.c \
|
||||
$(TOP)\ext\repair\sqlite3_checker.tcl \
|
||||
$(TOP)\ext\repair\checkindex.c \
|
||||
$(TOP)\ext\repair\checkfreelist.c \
|
||||
$(TOP)\ext\misc\btreeinfo.c \
|
||||
$(TOP)\ext\repair\sqlite3_checker.c.in
|
||||
|
||||
sqlite3_checker.c: $(CHECKER_DEPS)
|
||||
$(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\ext\repair\sqlite3_checker.c.in > $@
|
||||
|
||||
sqlite3_checker.exe: sqlite3_checker.c $(LIBRESOBJS)
|
||||
$(LTLINK) $(NO_WARN) -DBUILD_sqlite -I$(TCLINCDIR) sqlite3_checker.c \
|
||||
/link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS)
|
||||
|
||||
dbdump.exe: $(TOP)\ext\misc\dbdump.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
|
||||
$(LTLINK) $(NO_WARN) -DDBDUMP_STANDALONE $(TOP)\ext\misc\dbdump.c $(SQLITE3C) \
|
||||
/link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LTLIBS)
|
||||
@@ -2749,6 +2725,14 @@ speedtest1.exe: $(TOP)\test\speedtest1.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(ST_COMPILE_OPTS) -DSQLITE_OMIT_LOAD_EXTENSION \
|
||||
$(TOP)\test\speedtest1.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
fp-speed-1.exe: $(TOP)\test\fp-speed-1.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(ST_COMPILE_OPTS) -DSQLITE_OMIT_LOAD_EXTENSION \
|
||||
$(TOP)\test\fp-speed-1.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
fp-speed-2.exe: $(TOP)\test\fp-speed-2.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(ST_COMPILE_OPTS) -DSQLITE_OMIT_LOAD_EXTENSION \
|
||||
$(TOP)\test\fp-speed-2.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
kvtest.exe: $(TOP)\test\kvtest.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(KV_COMPILE_OPTS) \
|
||||
$(TOP)\test\kvtest.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
@@ -2938,6 +2922,7 @@ clean:
|
||||
del /Q LogEst.exe fts3view.exe rollback-test.exe showdb.exe dbdump.exe 2>NUL
|
||||
del /Q changeset.exe 2>NUL
|
||||
del /Q showjournal.exe showstat4.exe showwal.exe speedtest1.exe 2>NUL
|
||||
del /Q fp-speed-1.exe fp-speed-2.exe 2>NUL
|
||||
del /Q mptester.exe wordcount.exe rbu.exe srcck1.exe 2>NUL
|
||||
del /Q sqlite3.c sqlite3-*.c sqlite3.h 2>NUL
|
||||
del /Q sqlite3rc.h 2>NUL
|
||||
@@ -2952,4 +2937,5 @@ clean:
|
||||
del /Q fts5.* fts5parse.* 2>NUL
|
||||
del /q src-verify.exe 2>NUL
|
||||
del /q jimsh.exe jimsh0.exe 2>NUL
|
||||
-$(TCLSH_CMD) test/testrunner.tcl clean
|
||||
# <</mark>>
|
||||
|
||||
@@ -80,8 +80,8 @@ Then run commands like this:
|
||||
fossil open https://sqlite.org/src
|
||||
|
||||
The initial "fossil open" command will take two or three minutes. Afterwards,
|
||||
you can do fast, bandwidth-efficient updates to the whatever versions
|
||||
of SQLite you like. Some examples:
|
||||
you can do fast, bandwidth-efficient updates to whatever versions of SQLite you
|
||||
like. Some examples:
|
||||
|
||||
fossil update trunk ;# latest trunk check-in
|
||||
fossil update release ;# latest official release
|
||||
@@ -97,6 +97,8 @@ the build products. It is recommended, but not required, that the
|
||||
build directory be separate from the source directory. Cd into the
|
||||
build directory and then from the build directory run the configure
|
||||
script found at the root of the source tree. Then run "make".
|
||||
See the [compile-for-unix.md](doc/compile-for-unix.md) document for
|
||||
more detail.
|
||||
|
||||
For example:
|
||||
|
||||
|
||||
+1
-1
@@ -461,4 +461,4 @@ all other significant processing.
|
||||
[sqlite-config.tcl]: /file/autosetup/sqlite-config.tcl
|
||||
[Makefile.in]: /file/Makefile.in
|
||||
[main.mk]: /file/main.mk
|
||||
[JimTCL]: https://jim.tcl.tk
|
||||
[JimTCL]: https://msteveb.github.io/jimtcl/
|
||||
|
||||
+1657
-972
File diff suppressed because it is too large
Load Diff
@@ -743,12 +743,11 @@ proc sqlite-setup-default-cflags {} {
|
||||
# compiling binaries for the target system (CC a.k.a. $(T.cc)).
|
||||
# Normally they're the same, but they will differ when
|
||||
# cross-compiling.
|
||||
#
|
||||
# When cross-compiling we default to not using the -g flag, based on a
|
||||
# /chat discussion prompted by
|
||||
# https://sqlite.org/forum/forumpost/9a67df63eda9925c
|
||||
set defaultCFlags {-O2}
|
||||
if {!$::sqliteConfig(is-cross-compiling)} {
|
||||
# When cross-compiling we default to not using the -g flag, based
|
||||
# on a /chat discussion prompted by
|
||||
# https://sqlite.org/forum/forumpost/9a67df63eda9925c
|
||||
lappend defaultCFlags -g
|
||||
}
|
||||
define CFLAGS [proj-get-env CFLAGS $defaultCFlags]
|
||||
@@ -1138,6 +1137,27 @@ proc sqlite-check-line-editing {} {
|
||||
set editLibName "readline" ; # "readline" or "editline"
|
||||
set editLibDef "HAVE_READLINE" ; # "HAVE_READLINE" or "HAVE_EDITLINE"
|
||||
set dirLn [opt-val with-linenoise]
|
||||
|
||||
# If none of --with-linenoise, --enable-readline, or --enable-editline
|
||||
# are provided, but there exists a directory "linenoise" at $HOME or
|
||||
# a sibling of the build or source directory, then try to use that linenoise
|
||||
# direcctory.
|
||||
#
|
||||
if {"" eq $dirLn
|
||||
&& ![proj-opt-was-provided readline]
|
||||
&& ![proj-opt-was-provided editline]
|
||||
} {
|
||||
set dirlist ../linenoise
|
||||
catch {lappend dirlist [file-normalize $::autosetup(srcdir)/../linenoise]}
|
||||
catch {lappend dirlist $::env(HOME)/linenoise}
|
||||
foreach d $dirlist {
|
||||
if {[file exists $d/linenoise.c] && [file exists $d/linenoise.h]} {
|
||||
set dirLn $d
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if {"" ne $dirLn} {
|
||||
# Use linenoise from a copy of its sources (not a library)...
|
||||
if {![file isdir $dirLn]} {
|
||||
@@ -1173,6 +1193,8 @@ proc sqlite-check-line-editing {} {
|
||||
if {$::sqliteConfig(use-jim-for-codegen) && 2 == $lnVal} {
|
||||
define-append CFLAGS_JIMSH -DUSE_LINENOISE [get-define CFLAGS_READLINE]
|
||||
user-notice "Adding linenoise support to jimsh."
|
||||
} else {
|
||||
msg-result "Using linenoise at [file-normalize $dirLn]"
|
||||
}
|
||||
return "linenoise ($flavor)"
|
||||
} elseif {[opt-bool editline]} {
|
||||
|
||||
+28
-9
@@ -37,18 +37,35 @@ guidance on building for Windows.
|
||||
4. Download the SQLite source tree and unpack it. CD into the
|
||||
toplevel directory of the source tree.
|
||||
|
||||
5. Run: `./configure --enable-all --with-tclsh=$HOME/local/bin/tclsh9.0`
|
||||
5. *(Optional):* Download Antirez's "linenoise" command-line editing
|
||||
library to provide command-line editing in the CLI. You can find
|
||||
a suitable copy of the linenoise sources at
|
||||
<https://sqlite.org/linenoise.tar.gz> or at various other locations
|
||||
on the internet. If you put the linenoise source tree in
|
||||
a directory named $HOME/linenoise or a directory "linenoise" which
|
||||
is a sibling of the SQLite source tree, then the SQLite ./configure
|
||||
script will automatically find and use that source code to provide
|
||||
command-line editing in the CLI. If you would rather use the readline
|
||||
or editline libraries or a precompiled linenoise library, there are
|
||||
./configure options to accommodate that choice. The SQLite developers
|
||||
typically use $HOME/linenoise since linenoise is small, has no
|
||||
external dependencies, "just works", and the ./configure script
|
||||
will pick it up and use it automatically. But you do what works
|
||||
best for you.
|
||||
<p>
|
||||
You are not required to have any command-line editing support in
|
||||
order to use SQLite. But command-line editing does make the
|
||||
interactive experience more enjoyable.
|
||||
|
||||
6. Run: `./configure --enable-all --with-tclsh=$HOME/local/bin/tclsh9.0`
|
||||
|
||||
You do not need to use --with-tclsh if the tclsh you want to use is the
|
||||
first one on your PATH or if you are building without TCL.
|
||||
|
||||
The SQLite developers typically add the
|
||||
  `--with-linenoise=$HOME/linenoise`   option
|
||||
to provide command-line editing. "$HOME/linenoise" is a directory
|
||||
that contains [linenoise](https://github.com/antirez/linenoise) source
|
||||
code files, `linenoise.c` and `linenoise.h`.
|
||||
Lots of other options to ./configure are available.
|
||||
Run `./configure --help` for further guidance.
|
||||
|
||||
6. Run the "`Makefile`" makefile with an appropriate target.
|
||||
7. Run the "`Makefile`" makefile with an appropriate target.
|
||||
Examples:
|
||||
<ul>
|
||||
<li> `make sqlite3.c`
|
||||
@@ -73,5 +90,7 @@ guidance on building for Windows.
|
||||
of SQLite.
|
||||
|
||||
|
||||
7. For a debugging build of the CLI, where the ".treetrace" and ".wheretrace"
|
||||
commands work, add the the --with-debug argument to configure.
|
||||
8. For a debugging build of the CLI, use `./configure --dev`. A debugging
|
||||
build contains lots of extra debugging code, so it is slow and a lot
|
||||
bigger. You probably do not want to deploy a debugging build. But if
|
||||
you are working on the code, a debugging build works much better.
|
||||
|
||||
+10
-1
@@ -203,7 +203,16 @@ typedef sqlite3_int64 i64; /* 8-byte signed integer */
|
||||
#define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32))
|
||||
#define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64)
|
||||
|
||||
#define deliberate_fall_through
|
||||
#if !defined(deliberate_fall_through)
|
||||
# if defined(__has_attribute)
|
||||
# if __has_attribute(fallthrough)
|
||||
# define deliberate_fall_through __attribute__((fallthrough));
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(deliberate_fall_through)
|
||||
# define deliberate_fall_through
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Macros needed to provide flexible arrays in a portable way
|
||||
|
||||
+1
-1
@@ -325,7 +325,7 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){
|
||||
pCsr->aStat[1].nDoc++;
|
||||
}
|
||||
eState = 2;
|
||||
/* fall through */
|
||||
/* no break */ deliberate_fall_through
|
||||
|
||||
case 2:
|
||||
if( v==0 ){ /* 0x00. Next integer will be a docid. */
|
||||
|
||||
+1
-1
@@ -455,7 +455,7 @@ static void fts5SnippetFunction(
|
||||
|
||||
iBestCol = (iCol>=0 ? iCol : 0);
|
||||
nPhrase = pApi->xPhraseCount(pFts);
|
||||
aSeen = sqlite3_malloc(nPhrase);
|
||||
aSeen = sqlite3_malloc64(nPhrase);
|
||||
if( aSeen==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){
|
||||
if( nIn<0 ){
|
||||
nIn = (int)strlen(pIn);
|
||||
}
|
||||
zRet = (char*)sqlite3_malloc(nIn+1);
|
||||
zRet = (char*)sqlite3_malloc64((i64)nIn+1);
|
||||
if( zRet ){
|
||||
memcpy(zRet, pIn, nIn);
|
||||
zRet[nIn] = '\0';
|
||||
|
||||
@@ -576,7 +576,7 @@ int sqlite3Fts5ConfigParse(
|
||||
sqlite3_int64 nByte;
|
||||
int bUnindexed = 0; /* True if there are one or more UNINDEXED */
|
||||
|
||||
*ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config));
|
||||
*ppOut = pRet = (Fts5Config*)sqlite3_malloc64(sizeof(Fts5Config));
|
||||
if( pRet==0 ) return SQLITE_NOMEM;
|
||||
memset(pRet, 0, sizeof(Fts5Config));
|
||||
pRet->pGlobal = pGlobal;
|
||||
@@ -1123,5 +1123,3 @@ void sqlite3Fts5ConfigErrmsg(Fts5Config *pConfig, const char *zFmt, ...){
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ int sqlite3Fts5ExprNew(
|
||||
|
||||
assert( sParse.rc!=SQLITE_OK || sParse.zErr==0 );
|
||||
if( sParse.rc==SQLITE_OK ){
|
||||
*ppNew = pNew = sqlite3_malloc(sizeof(Fts5Expr));
|
||||
*ppNew = pNew = sqlite3_malloc64(sizeof(Fts5Expr));
|
||||
if( pNew==0 ){
|
||||
sParse.rc = SQLITE_NOMEM;
|
||||
sqlite3Fts5ParseNodeFree(sParse.pExpr);
|
||||
@@ -466,7 +466,7 @@ int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){
|
||||
p2->pRoot = 0;
|
||||
|
||||
if( sParse.rc==SQLITE_OK ){
|
||||
Fts5ExprPhrase **ap = (Fts5ExprPhrase**)sqlite3_realloc(
|
||||
Fts5ExprPhrase **ap = (Fts5ExprPhrase**)sqlite3_realloc64(
|
||||
p1->apExprPhrase, nPhrase * sizeof(Fts5ExprPhrase*)
|
||||
);
|
||||
if( ap==0 ){
|
||||
|
||||
@@ -91,7 +91,7 @@ int sqlite3Fts5HashNew(Fts5Config *pConfig, Fts5Hash **ppNew, int *pnByte){
|
||||
int rc = SQLITE_OK;
|
||||
Fts5Hash *pNew;
|
||||
|
||||
*ppNew = pNew = (Fts5Hash*)sqlite3_malloc(sizeof(Fts5Hash));
|
||||
*ppNew = pNew = (Fts5Hash*)sqlite3_malloc64(sizeof(Fts5Hash));
|
||||
if( pNew==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
|
||||
+15
-14
@@ -2093,7 +2093,7 @@ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){
|
||||
|
||||
/* If necessary, grow the pIter->aRowidOffset[] array. */
|
||||
if( iRowidOffset>=pIter->nRowidOffset ){
|
||||
int nNew = pIter->nRowidOffset + 8;
|
||||
i64 nNew = pIter->nRowidOffset + 8;
|
||||
int *aNew = (int*)sqlite3_realloc64(pIter->aRowidOffset,nNew*sizeof(int));
|
||||
if( aNew==0 ){
|
||||
p->rc = SQLITE_NOMEM;
|
||||
@@ -6418,16 +6418,16 @@ struct Fts5TokenDataMap {
|
||||
** aMap[] variables.
|
||||
*/
|
||||
struct Fts5TokenDataIter {
|
||||
int nMapAlloc; /* Allocated size of aMap[] in entries */
|
||||
int nMap; /* Number of valid entries in aMap[] */
|
||||
i64 nMapAlloc; /* Allocated size of aMap[] in entries */
|
||||
i64 nMap; /* Number of valid entries in aMap[] */
|
||||
Fts5TokenDataMap *aMap; /* Array of (rowid+pos -> token) mappings */
|
||||
|
||||
/* The following are used for prefix-queries only. */
|
||||
Fts5Buffer terms;
|
||||
|
||||
/* The following are used for other full-token tokendata queries only. */
|
||||
int nIter;
|
||||
int nIterAlloc;
|
||||
i64 nIter;
|
||||
i64 nIterAlloc;
|
||||
Fts5PoslistReader *aPoslistReader;
|
||||
int *aPoslistToIter;
|
||||
Fts5Iter *apIter[FLEXARRAY];
|
||||
@@ -6483,11 +6483,11 @@ static void fts5TokendataIterAppendMap(
|
||||
){
|
||||
if( p->rc==SQLITE_OK ){
|
||||
if( pT->nMap==pT->nMapAlloc ){
|
||||
int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64;
|
||||
int nAlloc = nNew * sizeof(Fts5TokenDataMap);
|
||||
i64 nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64;
|
||||
i64 nAlloc = nNew * sizeof(Fts5TokenDataMap);
|
||||
Fts5TokenDataMap *aNew;
|
||||
|
||||
aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nAlloc);
|
||||
aNew = (Fts5TokenDataMap*)sqlite3_realloc64(pT->aMap, nAlloc);
|
||||
if( aNew==0 ){
|
||||
p->rc = SQLITE_NOMEM;
|
||||
return;
|
||||
@@ -6513,7 +6513,7 @@ static void fts5TokendataIterAppendMap(
|
||||
*/
|
||||
static void fts5TokendataIterSortMap(Fts5Index *p, Fts5TokenDataIter *pT){
|
||||
Fts5TokenDataMap *aTmp = 0;
|
||||
int nByte = pT->nMap * sizeof(Fts5TokenDataMap);
|
||||
i64 nByte = pT->nMap * sizeof(Fts5TokenDataMap);
|
||||
|
||||
aTmp = (Fts5TokenDataMap*)sqlite3Fts5MallocZero(&p->rc, nByte);
|
||||
if( aTmp ){
|
||||
@@ -7047,9 +7047,10 @@ static Fts5TokenDataIter *fts5AppendTokendataIter(
|
||||
|
||||
if( p->rc==SQLITE_OK ){
|
||||
if( pIn==0 || pIn->nIter==pIn->nIterAlloc ){
|
||||
int nAlloc = pIn ? pIn->nIterAlloc*2 : 16;
|
||||
int nByte = SZ_FTS5TOKENDATAITER(nAlloc+1);
|
||||
Fts5TokenDataIter *pNew = (Fts5TokenDataIter*)sqlite3_realloc(pIn, nByte);
|
||||
i64 nAlloc = pIn ? pIn->nIterAlloc*2 : 16;
|
||||
i64 nByte = SZ_FTS5TOKENDATAITER(nAlloc+1);
|
||||
Fts5TokenDataIter *pNew;
|
||||
pNew = (Fts5TokenDataIter*)sqlite3_realloc64(pIn, nByte);
|
||||
|
||||
if( pNew==0 ){
|
||||
p->rc = SQLITE_NOMEM;
|
||||
@@ -7146,8 +7147,8 @@ static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){
|
||||
|
||||
/* Ensure the token-mapping is large enough */
|
||||
if( eDetail==FTS5_DETAIL_FULL && pT->nMapAlloc<(pT->nMap + nByte) ){
|
||||
int nNew = (pT->nMapAlloc + nByte) * 2;
|
||||
Fts5TokenDataMap *aNew = (Fts5TokenDataMap*)sqlite3_realloc(
|
||||
i64 nNew = (pT->nMapAlloc + nByte) * 2;
|
||||
Fts5TokenDataMap *aNew = (Fts5TokenDataMap*)sqlite3_realloc64(
|
||||
pT->aMap, nNew*sizeof(Fts5TokenDataMap)
|
||||
);
|
||||
if( aNew==0 ){
|
||||
|
||||
+47
-22
@@ -517,7 +517,7 @@ static void fts5SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){
|
||||
if( sqlite3_libversion_number()>=3008002 )
|
||||
#endif
|
||||
{
|
||||
pIdxInfo->estimatedRows = nRow;
|
||||
pIdxInfo->estimatedRows = MAX(1, nRow);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -586,19 +586,30 @@ static int fts5UsePatternMatch(
|
||||
** a) If a MATCH operator is present, the cost depends on the other
|
||||
** constraints also present. As follows:
|
||||
**
|
||||
** * No other constraints: cost=1000.0
|
||||
** * One rowid range constraint: cost=750.0
|
||||
** * Both rowid range constraints: cost=500.0
|
||||
** * An == rowid constraint: cost=100.0
|
||||
** * No other constraints: cost=50000.0
|
||||
** * One rowid range constraint: cost=37500.0
|
||||
** * Both rowid range constraints: cost=30000.0
|
||||
** * An == rowid constraint: cost=25000.0
|
||||
**
|
||||
** b) Otherwise, if there is no MATCH:
|
||||
**
|
||||
** * No other constraints: cost=1000000.0
|
||||
** * One rowid range constraint: cost=750000.0
|
||||
** * Both rowid range constraints: cost=250000.0
|
||||
** * An == rowid constraint: cost=10.0
|
||||
** * No other constraints: cost=3000000.0
|
||||
** * One rowid range constraints: cost=2250000.0
|
||||
** * Both rowid range constraint: cost=750000.0
|
||||
** * An == rowid constraint: cost=25.0
|
||||
**
|
||||
** Costs are not modified by the ORDER BY clause.
|
||||
**
|
||||
** The ratios used in case (a) are based on informal results obtained from
|
||||
** the tool/fts5cost.tcl script. The "MATCH and ==" combination has the
|
||||
** cost set quite high because the query may be a prefix query. Unless
|
||||
** there is a prefix index, prefix queries with rowid constraints are much
|
||||
** more expensive than non-prefix queries with rowid constraints.
|
||||
**
|
||||
** The estimated rows returned is set to the cost/40. For simple queries,
|
||||
** experimental results show that cost/4 might be about right. But for
|
||||
** more complex queries that use multiple terms the number of rows might
|
||||
** be far fewer than this. So we compromise and use cost/40.
|
||||
*/
|
||||
static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
|
||||
Fts5Table *pTab = (Fts5Table*)pVTab;
|
||||
@@ -631,7 +642,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
idxStr = (char*)sqlite3_malloc(pInfo->nConstraint * 8 + 1);
|
||||
idxStr = (char*)sqlite3_malloc64((i64)pInfo->nConstraint * 8 + 1);
|
||||
if( idxStr==0 ) return SQLITE_NOMEM;
|
||||
pInfo->idxStr = idxStr;
|
||||
pInfo->needToFreeIdxStr = 1;
|
||||
@@ -724,21 +735,35 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
|
||||
|
||||
/* Calculate the estimated cost based on the flags set in idxFlags. */
|
||||
if( bSeenEq ){
|
||||
pInfo->estimatedCost = nSeenMatch ? 1000.0 : 25.0;
|
||||
fts5SetUniqueFlag(pInfo);
|
||||
pInfo->estimatedCost = nSeenMatch ? 25000.0 : 25.0;
|
||||
fts5SetEstimatedRows(pInfo, 1);
|
||||
fts5SetUniqueFlag(pInfo);
|
||||
}else{
|
||||
if( bSeenLt && bSeenGt ){
|
||||
pInfo->estimatedCost = nSeenMatch ? 5000.0 : 750000.0;
|
||||
}else if( bSeenLt || bSeenGt ){
|
||||
pInfo->estimatedCost = nSeenMatch ? 7500.0 : 2250000.0;
|
||||
i64 nEstRows;
|
||||
if( nSeenMatch ){
|
||||
if( bSeenLt && bSeenGt ){
|
||||
pInfo->estimatedCost = 50000.0;
|
||||
}else if( bSeenLt || bSeenGt ){
|
||||
pInfo->estimatedCost = 37500.0;
|
||||
}else{
|
||||
pInfo->estimatedCost = 50000.0;
|
||||
}
|
||||
nEstRows = (i64)(pInfo->estimatedCost / 40.0);
|
||||
for(i=1; i<nSeenMatch; i++){
|
||||
pInfo->estimatedCost *= 2.5;
|
||||
nEstRows = nEstRows / 2;
|
||||
}
|
||||
}else{
|
||||
pInfo->estimatedCost = nSeenMatch ? 10000.0 : 3000000.0;
|
||||
if( bSeenLt && bSeenGt ){
|
||||
pInfo->estimatedCost = 750000.0;
|
||||
}else if( bSeenLt || bSeenGt ){
|
||||
pInfo->estimatedCost = 2250000.0;
|
||||
}else{
|
||||
pInfo->estimatedCost = 3000000.0;
|
||||
}
|
||||
nEstRows = (i64)(pInfo->estimatedCost / 4.0);
|
||||
}
|
||||
for(i=1; i<nSeenMatch; i++){
|
||||
pInfo->estimatedCost *= 0.4;
|
||||
}
|
||||
fts5SetEstimatedRows(pInfo, (i64)(pInfo->estimatedCost / 4.0));
|
||||
fts5SetEstimatedRows(pInfo, nEstRows);
|
||||
}
|
||||
|
||||
pInfo->idxNum = idxFlags;
|
||||
@@ -3763,7 +3788,7 @@ static int fts5Init(sqlite3 *db){
|
||||
int rc;
|
||||
Fts5Global *pGlobal = 0;
|
||||
|
||||
pGlobal = (Fts5Global*)sqlite3_malloc(sizeof(Fts5Global));
|
||||
pGlobal = (Fts5Global*)sqlite3_malloc64(sizeof(Fts5Global));
|
||||
if( pGlobal==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
|
||||
+4
-4
@@ -391,7 +391,7 @@ static int SQLITE_TCLAPI xF5tApi(
|
||||
break;
|
||||
}
|
||||
CASE(12, "xSetAuxdata") {
|
||||
F5tAuxData *pData = (F5tAuxData*)sqlite3_malloc(sizeof(F5tAuxData));
|
||||
F5tAuxData *pData = (F5tAuxData*)sqlite3_malloc64(sizeof(F5tAuxData));
|
||||
if( pData==0 ){
|
||||
Tcl_AppendResult(interp, "out of memory", (char*)0);
|
||||
return TCL_ERROR;
|
||||
@@ -780,7 +780,7 @@ static int SQLITE_TCLAPI f5tTokenize(
|
||||
}
|
||||
|
||||
if( nText>0 ){
|
||||
pCopy = sqlite3_malloc(nText);
|
||||
pCopy = sqlite3_malloc64(nText);
|
||||
if( pCopy==0 ){
|
||||
tokenizer.xDelete(pTok);
|
||||
Tcl_AppendResult(interp, "error in sqlite3_malloc()", (char*)0);
|
||||
@@ -1420,7 +1420,7 @@ static int f5tOrigintextCreate(
|
||||
void *pTokCtx = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
pTok = (OriginTextTokenizer*)sqlite3_malloc(sizeof(OriginTextTokenizer));
|
||||
pTok = (OriginTextTokenizer*)sqlite3_malloc64(sizeof(OriginTextTokenizer));
|
||||
if( pTok==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else if( nArg<1 ){
|
||||
@@ -1480,7 +1480,7 @@ static int xOriginToken(
|
||||
int nReq = nToken + 1 + (iEnd-iStart);
|
||||
if( nReq>p->nBuf ){
|
||||
sqlite3_free(p->aBuf);
|
||||
p->aBuf = sqlite3_malloc(nReq*2);
|
||||
p->aBuf = sqlite3_malloc64(nReq*2);
|
||||
if( p->aBuf==0 ) return SQLITE_NOMEM;
|
||||
p->nBuf = nReq*2;
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ static int fts5tokConnectMethod(
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
pTab = (Fts5tokTable*)sqlite3_malloc(sizeof(Fts5tokTable));
|
||||
pTab = (Fts5tokTable*)sqlite3_malloc64(sizeof(Fts5tokTable));
|
||||
if( pTab==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
@@ -275,7 +275,7 @@ static int fts5tokBestIndexMethod(
|
||||
static int fts5tokOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
|
||||
Fts5tokCursor *pCsr;
|
||||
|
||||
pCsr = (Fts5tokCursor *)sqlite3_malloc(sizeof(Fts5tokCursor));
|
||||
pCsr = (Fts5tokCursor *)sqlite3_malloc64(sizeof(Fts5tokCursor));
|
||||
if( pCsr==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
@@ -347,7 +347,7 @@ static int fts5tokCb(
|
||||
if( pCsr->nRow ){
|
||||
pRow->iPos = pRow[-1].iPos + ((tflags & FTS5_TOKEN_COLOCATED) ? 0 : 1);
|
||||
}
|
||||
pRow->zToken = sqlite3_malloc(nToken+1);
|
||||
pRow->zToken = sqlite3_malloc64((sqlite3_int64)nToken+1);
|
||||
if( pRow->zToken==0 ) return SQLITE_NOMEM;
|
||||
memcpy(pRow->zToken, pToken, nToken);
|
||||
pRow->zToken[nToken] = 0;
|
||||
@@ -373,8 +373,8 @@ static int fts5tokFilterMethod(
|
||||
fts5tokResetCursor(pCsr);
|
||||
if( idxNum==1 ){
|
||||
const char *zByte = (const char *)sqlite3_value_text(apVal[0]);
|
||||
int nByte = sqlite3_value_bytes(apVal[0]);
|
||||
pCsr->zInput = sqlite3_malloc(nByte+1);
|
||||
sqlite3_int64 nByte = sqlite3_value_bytes(apVal[0]);
|
||||
pCsr->zInput = sqlite3_malloc64(nByte+1);
|
||||
if( pCsr->zInput==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
|
||||
@@ -72,7 +72,7 @@ static int fts5AsciiCreate(
|
||||
if( nArg%2 ){
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
p = sqlite3_malloc(sizeof(AsciiTokenizer));
|
||||
p = sqlite3_malloc64(sizeof(AsciiTokenizer));
|
||||
if( p==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
@@ -367,7 +367,7 @@ static int fts5UnicodeCreate(
|
||||
if( nArg%2 ){
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
p = (Unicode61Tokenizer*)sqlite3_malloc(sizeof(Unicode61Tokenizer));
|
||||
p = (Unicode61Tokenizer*)sqlite3_malloc64(sizeof(Unicode61Tokenizer));
|
||||
if( p ){
|
||||
const char *zCat = "L* N* Co";
|
||||
int i;
|
||||
@@ -590,7 +590,7 @@ static int fts5PorterCreate(
|
||||
zBase = azArg[0];
|
||||
}
|
||||
|
||||
pRet = (PorterTokenizer*)sqlite3_malloc(sizeof(PorterTokenizer));
|
||||
pRet = (PorterTokenizer*)sqlite3_malloc64(sizeof(PorterTokenizer));
|
||||
if( pRet ){
|
||||
memset(pRet, 0, sizeof(PorterTokenizer));
|
||||
rc = pApi->xFindTokenizer_v2(pApi, zBase, &pUserdata, &pV2);
|
||||
@@ -1297,7 +1297,7 @@ static int fts5TriCreate(
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
int i;
|
||||
pNew = (TrigramTokenizer*)sqlite3_malloc(sizeof(*pNew));
|
||||
pNew = (TrigramTokenizer*)sqlite3_malloc64(sizeof(*pNew));
|
||||
if( pNew==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
|
||||
@@ -666,7 +666,7 @@ static int fts5VocabFilterMethod(
|
||||
const char *zCopy = (const char *)sqlite3_value_text(pLe);
|
||||
if( zCopy==0 ) zCopy = "";
|
||||
pCsr->nLeTerm = sqlite3_value_bytes(pLe);
|
||||
pCsr->zLeTerm = sqlite3_malloc(pCsr->nLeTerm+1);
|
||||
pCsr->zLeTerm = sqlite3_malloc64((i64)pCsr->nLeTerm+1);
|
||||
if( pCsr->zLeTerm==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
|
||||
@@ -379,9 +379,6 @@ do_execsql_test 12.2 {
|
||||
db close
|
||||
sqlite3 db test.db -readonly 1
|
||||
|
||||
explain_i {
|
||||
PRAGMA integrity_check
|
||||
}
|
||||
do_execsql_test 12.3 {
|
||||
PRAGMA integrity_check
|
||||
} {ok}
|
||||
|
||||
@@ -65,5 +65,14 @@ do_eqp_test 1.4 {
|
||||
`--SCAN vt VIRTUAL TABLE INDEX 0:=
|
||||
}
|
||||
|
||||
do_eqp_test 1.5 {
|
||||
SELECT * FROM vt, t1
|
||||
WHERE vt.rowid = t1.rowid AND vt MATCH ? AND b = ?
|
||||
} {
|
||||
QUERY PLAN
|
||||
|--SCAN vt VIRTUAL TABLE INDEX 0:M1
|
||||
`--SEARCH t1 USING INTEGER PRIMARY KEY (rowid=?)
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#
|
||||
# 2026 March 20
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
#
|
||||
# This script extracts the documentation for the API used by fts5 auxiliary
|
||||
# functions from header file fts5.h. It outputs html text on stdout that
|
||||
# is included in the documentation on the web.
|
||||
#
|
||||
|
||||
|
||||
sqlite3 db fts5cost.db
|
||||
|
||||
# Create an IPK table with 1,000,000 entries. Short records.
|
||||
#
|
||||
set res [list [catch { db eval {SELECT count(*) FROM t1} } msg] $msg]
|
||||
if {$res!="0 1000000"} {
|
||||
db eval {
|
||||
PRAGMA mmap_size = 1000000000; -- 1GB
|
||||
DROP TABLE IF EXISTS t1;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1_000_000
|
||||
)
|
||||
INSERT INTO t1 SELECT i, hex(randomblob(8)) FROM s;
|
||||
}
|
||||
}
|
||||
|
||||
# Create an FTS5 table with 1,000,000 entries. Each row contains a single
|
||||
# column containing a document of 100 terms chosen pseudo-randomly from
|
||||
# a vocabularly of 2000.
|
||||
set res [list [catch { db eval {SELECT count(*) FROM f1} } msg] $msg]
|
||||
if {$res!="0 1000000"} {
|
||||
set nVocab 2000
|
||||
set nTerm 100
|
||||
db eval {
|
||||
BEGIN;
|
||||
DROP TABLE IF EXISTS vocab1;
|
||||
CREATE TABLE vocab1(w);
|
||||
}
|
||||
for {set ii 0} {$ii<$nVocab} {incr ii} {
|
||||
set word [format %06x [expr {int(abs(rand()) * 0xFFFFFF)}]]
|
||||
db eval { INSERT INTO vocab1 VALUES($word) }
|
||||
lappend lVocab $word
|
||||
}
|
||||
db func doc doc
|
||||
proc doc {} {
|
||||
for {set ii 0} {$ii<$::nTerm} {incr ii} {
|
||||
lappend ret [lindex $::lVocab [expr int(abs(rand())*$::nVocab)]]
|
||||
}
|
||||
set ret
|
||||
}
|
||||
db eval {
|
||||
DROP TABLE IF EXISTS f1;
|
||||
CREATE VIRTUAL TABLE f1 USING fts5(x);
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1_000_000
|
||||
)
|
||||
INSERT INTO f1(rowid, x) SELECT i, doc() FROM s;
|
||||
COMMIT;
|
||||
}
|
||||
} else {
|
||||
set lVocab [db eval { SELECT * FROM vocab1 }]
|
||||
set nVocab [llength $lVocab]
|
||||
}
|
||||
|
||||
proc rowid_query {n} {
|
||||
set rowid 654
|
||||
for {set ii 0} {$ii<$n} {incr ii} {
|
||||
db eval { SELECT b FROM t1 WHERE a = $rowid }
|
||||
set rowid [expr {($rowid + 7717) % 1000000}]
|
||||
}
|
||||
}
|
||||
|
||||
proc rowid_query_fts {n} {
|
||||
set rowid 654
|
||||
for {set ii 0} {$ii<$n} {incr ii} {
|
||||
db eval { SELECT * FROM f1 WHERE rowid = $rowid }
|
||||
set rowid [expr {($rowid + 7717) % 1000000}]
|
||||
}
|
||||
}
|
||||
|
||||
proc match_query_fts {n} {
|
||||
set idx 654
|
||||
for {set ii 0} {$ii<$n} {incr ii} {
|
||||
set match [lrange $::lVocab $idx $idx+1]
|
||||
db eval { SELECT * FROM f1($match) }
|
||||
set idx [expr {($idx + 7717) % $::nVocab}]
|
||||
}
|
||||
}
|
||||
|
||||
proc prefix_query_fts {n} {
|
||||
set idx 654
|
||||
for {set ii 0} {$ii<$n} {incr ii} {
|
||||
set match "[lindex $::lVocab $idx]*"
|
||||
db eval { SELECT * FROM f1($match) }
|
||||
set idx [expr {($idx + 7717) % $::nVocab}]
|
||||
}
|
||||
}
|
||||
|
||||
proc match_rowid_query_fts {n} {
|
||||
set idx 654
|
||||
for {set ii 0} {$ii<$n} {incr ii} {
|
||||
set match "[lindex $::lVocab $idx]"
|
||||
db eval { SELECT * FROM f1($match) WHERE rowid=500000 }
|
||||
set idx [expr {($idx + 7717) % $::nVocab}]
|
||||
}
|
||||
}
|
||||
|
||||
proc prefix_rowid_query_fts {n} {
|
||||
set idx 654
|
||||
for {set ii 0} {$ii<$n} {incr ii} {
|
||||
set match "[lindex $::lVocab $idx]*"
|
||||
db eval { SELECT * FROM f1($match) WHERE rowid=500000 }
|
||||
set idx [expr {($idx + 7717) % $::nVocab}]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
proc mytime {cmd div} {
|
||||
set tm [time $cmd]
|
||||
expr {[lindex $tm 0] / $div}
|
||||
}
|
||||
|
||||
#set us [mytime { match_rowid_query_fts 1000 } 1000]
|
||||
#puts "1000 match/rowid queries on fts5 table: ${us} per query"
|
||||
|
||||
set us [mytime { prefix_rowid_query_fts 1000 } 1000]
|
||||
puts "1000 prefix/rowid queries on fts5 table: ${us} per query"
|
||||
|
||||
set us [mytime { match_query_fts 10 } 10]
|
||||
puts "10 match queries on fts5 table: ${us} per query"
|
||||
|
||||
set us [mytime { prefix_query_fts 10 } 10]
|
||||
puts "10 prefix queries on fts5 table: ${us} per query"
|
||||
|
||||
set us [mytime { prefix_rowid_query_fts 1000 } 1000]
|
||||
puts "1000 prefix/rowid queries on fts5 table: ${us} per query"
|
||||
|
||||
set us [mytime { rowid_query 10000 } 10000]
|
||||
puts "10000 by-rowid queries on normal table: ${us} per query"
|
||||
|
||||
set us [mytime { rowid_query_fts 10000 } 10000]
|
||||
puts "10000 by-rowid queries on fts5 table: ${us} per query"
|
||||
|
||||
|
||||
@@ -319,7 +319,7 @@ static int intckGetToken(const char *z){
|
||||
char c = z[0];
|
||||
int iRet = 1;
|
||||
if( c=='\'' || c=='"' || c=='`' ){
|
||||
while( 1 ){
|
||||
while( z[iRet] ){
|
||||
if( z[iRet]==c ){
|
||||
iRet++;
|
||||
if( z[iRet]!=c ) break;
|
||||
|
||||
+2
-2
@@ -847,7 +847,7 @@ static int amatchConnect(
|
||||
|
||||
(void)pAux;
|
||||
*ppVtab = 0;
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
rc = SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -928,7 +928,7 @@ amatchConnectError:
|
||||
static int amatchOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
|
||||
amatch_vtab *p = (amatch_vtab*)pVTab;
|
||||
amatch_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
pCur->pVtab = p;
|
||||
|
||||
@@ -0,0 +1,827 @@
|
||||
/*
|
||||
** 2026-04-13
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
** Partial reimplement of the sqlite3_analyzer utility program as
|
||||
** loadable SQL function.
|
||||
*/
|
||||
#include "sqlite3ext.h"
|
||||
SQLITE_EXTENSION_INIT1
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
|
||||
/*
|
||||
** State information for the analysis
|
||||
*/
|
||||
typedef struct Analysis Analysis;
|
||||
struct Analysis {
|
||||
sqlite3 *db; /* Database connection */
|
||||
sqlite3_context *context; /* SQL function context */
|
||||
sqlite3_str *pOut; /* Write output here */
|
||||
char *zSU; /* Name of the temp.space_used table */
|
||||
const char *zSchema; /* Schema to be analyzed */
|
||||
};
|
||||
|
||||
/*
|
||||
** Free all resources that the Analysis object references and
|
||||
** reset the Analysis object.
|
||||
**
|
||||
** Call this routine multiple times on the same Analysis object
|
||||
** is a harmless no-op, as long as the memory for the object itself
|
||||
** has not been freed.
|
||||
*/
|
||||
static void analysisReset(Analysis *p){
|
||||
if( p->zSU ){
|
||||
char *zSql = sqlite3_mprintf("DROP TABLE temp.%s;", p->zSU);
|
||||
if( zSql ){
|
||||
sqlite3_exec(p->db, zSql, 0, 0, 0);
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
}
|
||||
sqlite3_str_free(p->pOut);
|
||||
sqlite3_free(p->zSU);
|
||||
memset(p, 0, sizeof(*p));
|
||||
}
|
||||
|
||||
/*
|
||||
** Report an error using formatted text. If zFormat==NULL then report
|
||||
** an OOM error.
|
||||
*/
|
||||
static void analysisError(Analysis *p, const char *zFormat, ...){
|
||||
char *zErr;
|
||||
if( zFormat ){
|
||||
va_list ap;
|
||||
va_start(ap, zFormat);
|
||||
zErr = sqlite3_vmprintf(zFormat, ap);
|
||||
va_end(ap);
|
||||
}else{
|
||||
zErr = 0;
|
||||
}
|
||||
if( zErr==0 ){
|
||||
sqlite3_result_error_nomem(p->context);
|
||||
}else{
|
||||
sqlite3_result_error(p->context, zErr, -1);
|
||||
sqlite3_free(zErr);
|
||||
}
|
||||
analysisReset(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Prepare and return an SQL statement.
|
||||
*/
|
||||
static sqlite3_stmt *analysisVPrep(Analysis *p, const char *zFmt, va_list ap){
|
||||
char *zSql;
|
||||
int rc;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
zSql = sqlite3_vmprintf(zFmt, ap);
|
||||
if( zSql==0 ){ analysisError(p,0); return 0; }
|
||||
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
|
||||
if( rc ){
|
||||
analysisError(p, "SQL parse error: %s\nOriginal SQL: %s",
|
||||
sqlite3_errmsg(p->db), zSql);
|
||||
sqlite3_finalize(pStmt);
|
||||
analysisReset(p);
|
||||
pStmt = 0;
|
||||
}
|
||||
sqlite3_free(zSql);
|
||||
return pStmt;
|
||||
}
|
||||
static sqlite3_stmt *analysisPrepare(Analysis *p, const char *zFormat, ...){
|
||||
va_list ap;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
va_start(ap, zFormat);
|
||||
pStmt = analysisVPrep(p,zFormat,ap);
|
||||
va_end(ap);
|
||||
return pStmt;
|
||||
}
|
||||
|
||||
/*
|
||||
** If rc is something other than SQLITE_DONE or SQLITE_OK, then report
|
||||
** an error and return true.
|
||||
**
|
||||
** If rc is SQLITE_DONE or SQLITE_OK, then return false.
|
||||
**
|
||||
** The prepared statement is closed in either case.
|
||||
*/
|
||||
static int analysisStmtFinish(Analysis *p, int rc, sqlite3_stmt *pStmt){
|
||||
if( rc==SQLITE_DONE ){
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
if( rc!=SQLITE_OK || (rc = sqlite3_reset(pStmt))!=SQLITE_OK ){
|
||||
analysisError(p, "SQL run-time error: %s\nOriginal SQL: %s",
|
||||
sqlite3_errmsg(p->db), sqlite3_sql(pStmt));
|
||||
analysisReset(p);
|
||||
}
|
||||
sqlite3_finalize(pStmt);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Run SQL. Return the number of errors.
|
||||
*/
|
||||
static int analysisSql(Analysis *p, const char *zFormat, ...){
|
||||
va_list ap;
|
||||
int rc;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
va_start(ap, zFormat);
|
||||
pStmt = analysisVPrep(p,zFormat,ap);
|
||||
va_end(ap);
|
||||
if( pStmt==0 ) return 1;
|
||||
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){}
|
||||
if( rc==SQLITE_DONE ){
|
||||
rc = SQLITE_OK;
|
||||
}else{
|
||||
analysisError(p, "SQL run-time error: %s\nOriginal SQL: %s",
|
||||
sqlite3_errmsg(p->db), sqlite3_sql(pStmt));
|
||||
analysisReset(p);
|
||||
}
|
||||
sqlite3_finalize(pStmt);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Run an SQL query that returns an integer. Write that integer
|
||||
** into *piRes. Return the number of errors.
|
||||
*/
|
||||
static int analysisSqlInt(
|
||||
Analysis *p,
|
||||
sqlite3_int64 *piRes,
|
||||
const char *zFormat, ...
|
||||
){
|
||||
va_list ap;
|
||||
int rc;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
va_start(ap, zFormat);
|
||||
pStmt = analysisVPrep(p,zFormat,ap);
|
||||
va_end(ap);
|
||||
if( pStmt==0 ) return 1;
|
||||
rc = sqlite3_step(pStmt);
|
||||
if( rc==SQLITE_ROW ){
|
||||
*piRes = sqlite3_column_int64(pStmt, 0);
|
||||
rc = SQLITE_OK;
|
||||
}else if( rc==SQLITE_DONE ){
|
||||
rc = SQLITE_OK;
|
||||
}else{
|
||||
if( p->db ){
|
||||
/* p->db is NULL if there was some prior error */
|
||||
analysisError(p, "SQL run-time error: %s\nOriginal SQL: %s",
|
||||
sqlite3_errmsg(p->db), sqlite3_sql(pStmt));
|
||||
}
|
||||
analysisReset(p);
|
||||
}
|
||||
sqlite3_finalize(pStmt);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Add to the output a title line that contains the text determined
|
||||
** by the format string. If the output is initially empty, begin
|
||||
** the title line with "/" so that it forms the beginning of a C-style
|
||||
** comment. Otherwise begin with a new-line. Always finish with a
|
||||
** newline.
|
||||
*/
|
||||
static void analysisTitle(Analysis *p, const char *zFormat, ...){
|
||||
char *zFirst;
|
||||
char *zTitle;
|
||||
size_t nTitle;
|
||||
va_list ap;
|
||||
va_start(ap, zFormat);
|
||||
zTitle = sqlite3_vmprintf(zFormat, ap);
|
||||
va_end(ap);
|
||||
if( zTitle==0 ){
|
||||
analysisError(p, 0);
|
||||
return;
|
||||
}
|
||||
zFirst = sqlite3_str_length(p->pOut)==0 ? "/" : "\n*";
|
||||
nTitle = strlen(zTitle);
|
||||
if( nTitle>=75 ){
|
||||
sqlite3_str_appendf(p->pOut, "%s** %z\n\n", zFirst, zTitle);
|
||||
}else{
|
||||
int nExtra = 74 - (int)nTitle;
|
||||
sqlite3_str_appendf(p->pOut, "%s** %z %.*c\n\n", zFirst, zTitle,
|
||||
nExtra, '*');
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Add an output line that begins with the zDesc text extended out to
|
||||
** 50 columns with "." characters, and followed by whatever text is
|
||||
** described by zFormat.
|
||||
*/
|
||||
static void analysisLine(
|
||||
Analysis *p, /* Analysis context */
|
||||
const char *zDesc, /* Description */
|
||||
const char *zFormat, /* Argument to the description */
|
||||
...
|
||||
){
|
||||
char *zTxt;
|
||||
size_t nDesc;
|
||||
va_list ap;
|
||||
va_start(ap, zFormat);
|
||||
zTxt = sqlite3_vmprintf(zFormat, ap);
|
||||
va_end(ap);
|
||||
if( zTxt==0 ){
|
||||
analysisError(p, 0);
|
||||
return;
|
||||
}
|
||||
nDesc = strlen(zDesc);
|
||||
if( nDesc>=50 ){
|
||||
sqlite3_str_appendf(p->pOut, "%s %z", zDesc, zTxt);
|
||||
}else{
|
||||
int nExtra = 50 - (int)nDesc;
|
||||
sqlite3_str_appendf(p->pOut, "%s%.*c %z", zDesc, nExtra, '.', zTxt);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Write a percentage into the output. The number written should show
|
||||
** two or three significant digits, with the decimal point being the fourth
|
||||
** character.
|
||||
*/
|
||||
static void analysisPercent(Analysis *p, double r){
|
||||
char zNum[100];
|
||||
char *zDP;
|
||||
int nLeadingDigit;
|
||||
int sz;
|
||||
sqlite3_snprintf(sizeof(zNum)-5, zNum, r>=10.0 ? "%.3g" :"%.2g", r);
|
||||
sz = (int)strlen(zNum);
|
||||
zDP = strchr(zNum, '.');
|
||||
if( zDP==0 ){
|
||||
memcpy(zNum+sz,".0",3);
|
||||
nLeadingDigit = sz;
|
||||
sz += 2;
|
||||
}else{
|
||||
nLeadingDigit = (int)(zDP - zNum);
|
||||
}
|
||||
if( nLeadingDigit<3 ){
|
||||
sqlite3_str_appendchar(p->pOut, 3-nLeadingDigit, ' ');
|
||||
}
|
||||
sqlite3_str_append(p->pOut, zNum, sz);
|
||||
sqlite3_str_append(p->pOut, "%\n", 2);
|
||||
}
|
||||
|
||||
/*
|
||||
** Create a subreport on a subset of tables and/or indexes.
|
||||
**
|
||||
** The title if the subreport is given by zTitle. zWhere is
|
||||
** a boolean expression that can go in the WHERE clause to select
|
||||
** the relevant rows of the s.zSU table.
|
||||
*/
|
||||
static int analysisSubreport(
|
||||
Analysis *p, /* Analysis context */
|
||||
char *zTitle, /* Title for this subreport */
|
||||
char *zWhere, /* WHERE clause for this subreport */
|
||||
sqlite3_int64 pgsz, /* Database page size */
|
||||
sqlite3_int64 nPage /* Number of pages in entire database */
|
||||
){
|
||||
sqlite3_stmt *pStmt; /* Statement to query p->zSU */
|
||||
sqlite3_int64 nentry; /* Number of btree entires */
|
||||
sqlite3_int64 payload; /* Payload in bytes */
|
||||
sqlite3_int64 ovfl_payload; /* overflow payload in bytes */
|
||||
sqlite3_int64 mx_payload; /* largest individual payload */
|
||||
sqlite3_int64 ovfl_cnt; /* Number entries using overflow */
|
||||
sqlite3_int64 leaf_pages; /* Leaf pages */
|
||||
sqlite3_int64 int_pages; /* internal pages */
|
||||
sqlite3_int64 ovfl_pages; /* overflow pages */
|
||||
sqlite3_int64 leaf_unused; /* unused bytes on leaf pages */
|
||||
sqlite3_int64 int_unused; /* unused bytes on internal pages */
|
||||
sqlite3_int64 ovfl_unused; /* unused bytes on overflow pages */
|
||||
sqlite3_int64 int_cell; /* B-tree entries on internal pages */
|
||||
sqlite3_int64 depth; /* btree depth */
|
||||
sqlite3_int64 cnt; /* Number of s.zSU entries that match */
|
||||
sqlite3_int64 storage; /* Total bytes */
|
||||
sqlite3_int64 total_pages; /* Total page count */
|
||||
sqlite3_int64 total_unused; /* Total unused bytes */
|
||||
sqlite3_int64 total_meta; /* Total metadata */
|
||||
int rc;
|
||||
|
||||
if( zTitle==0 || zWhere==0 ){
|
||||
analysisError(p, 0);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
pStmt = analysisPrepare(p,
|
||||
"SELECT\n"
|
||||
" sum(if(is_without_rowid OR is_index,nentry,leaf_entries)),\n" /* 0 */
|
||||
" sum(payload),\n" /* 1 */
|
||||
" sum(ovfl_payload),\n" /* 2 */
|
||||
" max(mx_payload),\n" /* 3 */
|
||||
" sum(ovfl_cnt),\n" /* 4 */
|
||||
" sum(leaf_pages),\n" /* 5 */
|
||||
" sum(int_pages),\n" /* 6 */
|
||||
" sum(ovfl_pages),\n" /* 7 */
|
||||
" sum(leaf_unused),\n" /* 8 */
|
||||
" sum(int_unused),\n" /* 9 */
|
||||
" sum(ovfl_unused),\n" /* 10 */
|
||||
" max(depth),\n" /* 11 */
|
||||
" count(*),\n" /* 12 */
|
||||
" sum(int_entries)\n" /* 13 */
|
||||
" FROM temp.%s WHERE %s",
|
||||
p->zSU, zWhere);
|
||||
if( pStmt==0 ) return 1;
|
||||
rc = sqlite3_step(pStmt);
|
||||
if( rc==SQLITE_ROW ){
|
||||
analysisTitle(p, zTitle);
|
||||
|
||||
nentry = sqlite3_column_int64(pStmt, 0);
|
||||
payload = sqlite3_column_int64(pStmt, 1);
|
||||
ovfl_payload = sqlite3_column_int64(pStmt, 2);
|
||||
mx_payload = sqlite3_column_int64(pStmt, 3);
|
||||
ovfl_cnt = sqlite3_column_int64(pStmt, 4);
|
||||
leaf_pages = sqlite3_column_int64(pStmt, 5);
|
||||
int_pages = sqlite3_column_int64(pStmt, 6);
|
||||
ovfl_pages = sqlite3_column_int64(pStmt, 7);
|
||||
leaf_unused = sqlite3_column_int64(pStmt, 8);
|
||||
int_unused = sqlite3_column_int64(pStmt, 9);
|
||||
ovfl_unused = sqlite3_column_int64(pStmt, 10);
|
||||
depth = sqlite3_column_int64(pStmt, 11);
|
||||
cnt = sqlite3_column_int64(pStmt, 12);
|
||||
int_cell = sqlite3_column_int64(pStmt, 13);
|
||||
rc = SQLITE_DONE;
|
||||
|
||||
total_pages = leaf_pages + int_pages + ovfl_pages;
|
||||
analysisLine(p, "Percentage of total database", "%.3g%%\n",
|
||||
(total_pages*100.0)/(double)nPage);
|
||||
analysisLine(p, "Number of entries", "%lld\n", nentry);
|
||||
storage = total_pages*pgsz;
|
||||
analysisLine(p, "Bytes of storage consumed", "%lld\n", storage);
|
||||
analysisLine(p, "Bytes of payload", "%-11lld ", payload);
|
||||
analysisPercent(p, payload*100.0/(double)storage);
|
||||
if( ovfl_cnt>0 ){
|
||||
analysisLine(p, "Bytes of payload in overflow","%-11lld ",ovfl_payload);
|
||||
analysisPercent(p, ovfl_payload*100.0/(double)payload);
|
||||
}
|
||||
total_unused = leaf_unused + int_unused + ovfl_unused;
|
||||
total_meta = storage - payload - total_unused;
|
||||
analysisLine(p, "Bytes of metadata","%-11lld ", total_meta);
|
||||
analysisPercent(p, total_meta*100.0/(double)storage);
|
||||
if( cnt==1 ){
|
||||
analysisLine(p, "B-tree depth", "%lld\n", depth);
|
||||
if( int_cell>1 ){
|
||||
analysisLine(p, "Average fanout", "%.1f\n",
|
||||
(double)(int_cell+int_pages)/(double)int_pages);
|
||||
}
|
||||
}
|
||||
if( nentry>0 ){
|
||||
analysisLine(p, "Average payload per entry", "%.1f\n",
|
||||
(double)payload/(double)nentry);
|
||||
analysisLine(p, "Average unused bytes per entry", "%.1f\n",
|
||||
(double)total_unused/(double)nentry);
|
||||
analysisLine(p, "Average metadata per entry", "%.1f\n",
|
||||
(double)total_meta/(double)nentry);
|
||||
}
|
||||
analysisLine(p, "Maximum single-entry payload", "%lld\n", mx_payload);
|
||||
if( nentry>0 ){
|
||||
analysisLine(p, "Entries that use overflow", "%-11lld ", ovfl_cnt);
|
||||
analysisPercent(p, ovfl_cnt*100.0/(double)nentry);
|
||||
}
|
||||
if( int_pages>0 ){
|
||||
analysisLine(p, "Index pages used", "%lld\n", int_pages);
|
||||
}
|
||||
analysisLine(p, "Primary pages used", "%lld\n", leaf_pages);
|
||||
if( ovfl_cnt ){
|
||||
analysisLine(p, "Overflow pages used", "%lld\n", ovfl_pages);
|
||||
}
|
||||
analysisLine(p, "Total pages used", "%lld\n", total_pages);
|
||||
if( int_pages>0 ){
|
||||
analysisLine(p, "Unused bytes on index pages", "%lld\n", int_unused);
|
||||
}
|
||||
analysisLine(p, "Unused bytes on primary pages", "%lld\n", leaf_unused);
|
||||
if( ovfl_cnt ){
|
||||
analysisLine(p, "Unused bytes on overflow pages", "%lld\n", ovfl_unused);
|
||||
}
|
||||
analysisLine(p, "Unused bytes on all pages", "%-11lld ", total_unused);
|
||||
analysisPercent(p, total_unused*100.0/(double)storage);
|
||||
}
|
||||
return analysisStmtFinish(p, rc, pStmt);
|
||||
}
|
||||
|
||||
/*
|
||||
** SQL Function: analyze(SCHEMA)
|
||||
**
|
||||
** Analyze the database schema named in the argument. Return text
|
||||
** containing the analysis.
|
||||
*/
|
||||
static void analyzeFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
int rc;
|
||||
sqlite3_stmt *pStmt;
|
||||
int n;
|
||||
sqlite3_int64 i64;
|
||||
sqlite3_int64 pgsz;
|
||||
sqlite3_int64 nPage;
|
||||
sqlite3_int64 nPageInUse;
|
||||
sqlite3_int64 nFreeList;
|
||||
sqlite3_int64 nIndex;
|
||||
sqlite3_int64 nWORowid;
|
||||
Analysis s;
|
||||
sqlite3_uint64 r[2];
|
||||
|
||||
memset(&s, 0, sizeof(s));
|
||||
s.db = sqlite3_context_db_handle(context);
|
||||
s.context = context;
|
||||
s.pOut = sqlite3_str_new(0);
|
||||
if( s.pOut==0 ){ analysisError(&s, 0); return; }
|
||||
s.zSchema = (const char*)sqlite3_value_text(argv[0]);
|
||||
if( s.zSchema==0 ){
|
||||
s.zSchema = "main";
|
||||
}else if( sqlite3_strlike("temp",s.zSchema,0)==0 ){
|
||||
/* Attempt to analyze "temp" returns NULL */
|
||||
analysisReset(&s);
|
||||
return;
|
||||
}
|
||||
i64 = 0;
|
||||
rc = analysisSqlInt(&s,&i64,"SELECT 1 FROM pragma_database_list"
|
||||
" WHERE name=%Q COLLATE nocase",s.zSchema);
|
||||
if( rc || i64==0 ){
|
||||
/* Return NULL the named schema does not exist */
|
||||
analysisReset(&s);
|
||||
return;
|
||||
}
|
||||
sqlite3_randomness(sizeof(r), &r);
|
||||
s.zSU = sqlite3_mprintf("analysis%016llx%016llx", r[0], r[1]);
|
||||
if( s.zSU==0 ){ analysisError(&s, 0); return; }
|
||||
|
||||
/* The s.zSU table contains the data used for the analysis.
|
||||
** The table name contains 128-bits of randomness to avoid
|
||||
** collisions with preexisting tables in temp.
|
||||
*/
|
||||
rc = analysisSql(&s,
|
||||
"CREATE TABLE temp.%s(\n"
|
||||
" name text, -- A table or index\n"
|
||||
" tblname text, -- Table that owns name\n"
|
||||
" is_index boolean, -- TRUE if it is an index\n"
|
||||
" is_without_rowid boolean, -- TRUE if WITHOUT ROWID table\n"
|
||||
" nentry int, -- Number of entries in the BTree\n"
|
||||
" leaf_entries int, -- Number of leaf entries\n"
|
||||
" depth int, -- Depth of the b-tree\n"
|
||||
" payload int, -- Total data stored in this table/index\n"
|
||||
" ovfl_payload int, -- Total data stored on overflow pages\n"
|
||||
" ovfl_cnt int, -- Number of entries that use overflow\n"
|
||||
" mx_payload int, -- Maximum payload size\n"
|
||||
" int_pages int, -- Interior pages used\n"
|
||||
" leaf_pages int, -- Leaf pages used\n"
|
||||
" ovfl_pages int, -- Overflow pages used\n"
|
||||
" int_unused int, -- Unused bytes on interior pages\n"
|
||||
" leaf_unused int, -- Unused bytes on primary pages\n"
|
||||
" ovfl_unused int, -- Unused bytes on overflow pages\n"
|
||||
" int_entries int -- Btree cells on internal pages\n"
|
||||
");",
|
||||
s.zSU
|
||||
);
|
||||
if( rc ) return;
|
||||
|
||||
/* Populate the s.zSU table
|
||||
*/
|
||||
rc = analysisSql(&s,
|
||||
"WITH\n"
|
||||
" allidx(idxname) AS (\n"
|
||||
" SELECT name FROM \"%w\".sqlite_schema WHERE type='index'\n"
|
||||
" ),\n"
|
||||
" allobj(allname,tblname,isidx,isworowid) AS (\n"
|
||||
" SELECT 'sqlite_schema',\n"
|
||||
" 'sqlite_schema',\n"
|
||||
" 0,\n"
|
||||
" 0\n"
|
||||
" UNION ALL\n"
|
||||
" SELECT name,\n"
|
||||
" tbl_name,\n"
|
||||
" type='index',\n"
|
||||
" EXISTS(SELECT 1\n"
|
||||
" FROM pragma_index_list(sqlite_schema.name,%Q)\n"
|
||||
" WHERE pragma_index_list.origin='pk'\n"
|
||||
" AND pragma_index_list.name NOT IN allidx)\n"
|
||||
" FROM \"%w\".sqlite_schema\n"
|
||||
" )\n"
|
||||
"INSERT INTO temp.%s\n"
|
||||
" SELECT\n"
|
||||
" allname,\n"
|
||||
" tblname,\n"
|
||||
" isidx,\n"
|
||||
" isworowid,\n"
|
||||
" sum(ncell),\n"
|
||||
" sum((pagetype='leaf')*ncell),\n"
|
||||
" max((length(if(path GLOB '*+*','',path))+3)/4),\n"
|
||||
" sum(payload),\n"
|
||||
" sum((pagetype='overflow')*payload),\n"
|
||||
" sum(path GLOB '*+000000'),\n"
|
||||
" max(mx_payload),\n"
|
||||
" sum(pagetype='internal'),\n"
|
||||
" sum(pagetype='leaf'),\n"
|
||||
" sum(pagetype='overflow'),\n"
|
||||
" sum((pagetype='internal')*unused),\n"
|
||||
" sum((pagetype='leaf')*unused),\n"
|
||||
" sum((pagetype='overflow')*unused),\n"
|
||||
" sum(if(pagetype='internal',ncell))\n"
|
||||
" FROM allobj CROSS JOIN dbstat(%Q) \n"
|
||||
" WHERE dbstat.name=allobj.allname\n"
|
||||
" GROUP BY allname;\n",
|
||||
s.zSchema, /* %w.sqlite_schema -- in allidx */
|
||||
s.zSchema, /* pragma_index_list(...,%Q) */
|
||||
s.zSchema, /* %w.sqlite_schema */
|
||||
s.zSU, /* INTO temp.%s */
|
||||
s.zSchema /* JOIN dbstat(%Q) */
|
||||
);
|
||||
if( rc ) return;
|
||||
|
||||
/* Begin generating the report */
|
||||
analysisTitle(&s, "Database storage utilization report");
|
||||
pgsz = 0;
|
||||
rc = analysisSqlInt(&s, &pgsz, "PRAGMA \"%w\".page_size", s.zSchema);
|
||||
if( rc ) return;
|
||||
analysisLine(&s, "Page size in bytes","%lld\n",pgsz);
|
||||
|
||||
nPage = 0;
|
||||
rc = analysisSqlInt(&s, &nPage, "PRAGMA \"%w\".page_count", s.zSchema);
|
||||
if( rc ) return;
|
||||
analysisLine(&s, "Pages in the database", "%lld\n", nPage);
|
||||
if( nPage<=0 ) nPage = 1;
|
||||
|
||||
nPageInUse = 0;
|
||||
rc = analysisSqlInt(&s, &nPageInUse,
|
||||
"SELECT sum(leaf_pages+int_pages+ovfl_pages) FROM temp.%s", s.zSU);
|
||||
if( rc ) return;
|
||||
analysisLine(&s, "Pages that store data", "%-11lld ", nPageInUse);
|
||||
analysisPercent(&s, (nPageInUse*100.0)/(double)nPage);
|
||||
|
||||
nFreeList = 0;
|
||||
rc = analysisSqlInt(&s, &nFreeList, "PRAGMA \"%w\".freelist_count",s.zSchema);
|
||||
if( rc ) return;
|
||||
analysisLine(&s, "Pages on the freelist", "%-11lld ", nFreeList);
|
||||
analysisPercent(&s, (nFreeList*100.0)/(double)nPage);
|
||||
|
||||
i64 = 0;
|
||||
rc = analysisSqlInt(&s, &i64, "PRAGMA \"%w\".auto_vacuum", s.zSchema);
|
||||
if( rc ) return;
|
||||
if( i64==0 || nPage<=1 ){
|
||||
i64 = 0;
|
||||
}else{
|
||||
double rPtrsPerPage = pgsz/5;
|
||||
double rAvPage = (nPage-1.0)/(rPtrsPerPage+1.0);
|
||||
i64 = (sqlite3_int64)ceil(rAvPage);
|
||||
}
|
||||
analysisLine(&s, "Pages of auto-vacuum overhead", "%-11lld ", i64);
|
||||
analysisPercent(&s, (i64*100.0)/(double)nPage);
|
||||
|
||||
i64 = 0;
|
||||
rc = analysisSqlInt(&s, &i64,
|
||||
"SELECT count(*)+1 FROM \"%w\".sqlite_schema WHERE type='table'",
|
||||
s.zSchema);
|
||||
if( rc ) return;
|
||||
analysisLine(&s, "Number of tables", "%lld\n", i64);
|
||||
nWORowid = 0;
|
||||
rc = analysisSqlInt(&s, &nWORowid,
|
||||
"SELECT count(*) FROM \"%w\".pragma_table_list WHERE wr",
|
||||
s.zSchema);
|
||||
if( rc ) return;
|
||||
if( nWORowid>0 ){
|
||||
analysisLine(&s, "Number of WITHOUT ROWID tables", "%lld\n", nWORowid);
|
||||
analysisLine(&s, "Number of rowid tables", "%lld\n", i64 - nWORowid);
|
||||
}
|
||||
nIndex = 0;
|
||||
rc = analysisSqlInt(&s, &nIndex,
|
||||
"SELECT count(*) FROM \"%w\".sqlite_schema WHERE type='index'",
|
||||
s.zSchema);
|
||||
if( rc ) return;
|
||||
analysisLine(&s, "Number of indexes", "%lld\n", nIndex);
|
||||
i64 = 0;
|
||||
rc = analysisSqlInt(&s, &i64,
|
||||
"SELECT count(*) FROM \"%w\".sqlite_schema"
|
||||
" WHERE name GLOB 'sqlite_autoindex_*' AND type='index'",
|
||||
s.zSchema);
|
||||
if( rc ) return;
|
||||
analysisLine(&s, "Number of defined indexes", "%lld\n", nIndex - i64);
|
||||
analysisLine(&s, "Number of implied indexes", "%lld\n", i64);
|
||||
analysisLine(&s, "Size of the database in bytes", "%lld\n", pgsz*nPage);
|
||||
i64 = 0;
|
||||
rc = analysisSqlInt(&s, &i64,
|
||||
"SELECT sum(payload) FROM temp.%s"
|
||||
" WHERE NOT is_index AND name NOT LIKE 'sqlite_schema'",
|
||||
s.zSU);
|
||||
if( rc ) return;
|
||||
analysisLine(&s, "Bytes of payload", "%-11lld ", i64);
|
||||
analysisPercent(&s, i64*100.0/(double)(pgsz*nPage));
|
||||
|
||||
analysisTitle(&s, "Page counts for all tables with their indexes");
|
||||
pStmt = analysisPrepare(&s,
|
||||
"SELECT upper(tblname),\n"
|
||||
" sum(int_pages+leaf_pages+ovfl_pages)\n"
|
||||
" FROM temp.%s\n"
|
||||
" GROUP BY 1\n"
|
||||
" ORDER BY 2 DESC, 1;",
|
||||
s.zSU);
|
||||
if( pStmt==0 ) return;
|
||||
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
|
||||
sqlite3_int64 n = sqlite3_column_int64(pStmt,1);
|
||||
analysisLine(&s, (const char*)sqlite3_column_text(pStmt,0), "%-11lld ", n);
|
||||
analysisPercent(&s, (n*100.0)/(double)nPage);
|
||||
}
|
||||
if( analysisStmtFinish(&s, rc, pStmt) ) return;
|
||||
|
||||
analysisTitle(&s, "Page counts for all tables and indexes separately");
|
||||
pStmt = analysisPrepare(&s,
|
||||
"SELECT upper(name),\n"
|
||||
" sum(int_pages+leaf_pages+ovfl_pages)\n"
|
||||
" FROM temp.%s\n"
|
||||
" GROUP BY 1\n"
|
||||
" ORDER BY 2 DESC, 1;",
|
||||
s.zSU);
|
||||
if( pStmt==0 ) return;
|
||||
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
|
||||
sqlite3_int64 n = sqlite3_column_int64(pStmt,1);
|
||||
analysisLine(&s, (const char*)sqlite3_column_text(pStmt,0), "%-11lld ", n);
|
||||
analysisPercent(&s, (n*100.0)/(double)nPage);
|
||||
}
|
||||
if( analysisStmtFinish(&s, rc, pStmt) ) return;
|
||||
|
||||
rc = analysisSubreport(&s, "All tables and indexes", "1", pgsz, nPage);
|
||||
if( rc ) return;
|
||||
rc = analysisSubreport(&s, "All tables", "NOT is_index", pgsz, nPage);
|
||||
if( rc ) return;
|
||||
if( nWORowid>0 ){
|
||||
rc = analysisSubreport(&s, "All WITHOUT ROWID tables", "is_without_rowid",
|
||||
pgsz, nPage);
|
||||
if( rc ) return;
|
||||
rc = analysisSubreport(&s, "All rowid tables",
|
||||
"NOT is_without_rowid AND NOT is_index",
|
||||
pgsz, nPage);
|
||||
if( rc ) return;
|
||||
}
|
||||
rc = analysisSubreport(&s, "All indexes", "is_index", pgsz, nPage);
|
||||
if( rc ) return;
|
||||
|
||||
pStmt = analysisPrepare(&s,
|
||||
"SELECT upper(tblname), tblname, sum(is_index) FROM temp.%s"
|
||||
" GROUP BY 1 ORDER BY 1",
|
||||
s.zSU);
|
||||
if( pStmt==0 ) return;
|
||||
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
|
||||
const char *zUpper = (const char*)sqlite3_column_text(pStmt, 0);
|
||||
const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
|
||||
int nSubIndex = sqlite3_column_int(pStmt, 2);
|
||||
if( nSubIndex==0 ){
|
||||
char *zTitle = sqlite3_mprintf("Table %s", zUpper);
|
||||
char *zWhere = sqlite3_mprintf("name=%Q", zName);
|
||||
rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage);
|
||||
sqlite3_free(zTitle);
|
||||
sqlite3_free(zWhere);
|
||||
if( rc ) break;
|
||||
}else{
|
||||
sqlite3_stmt *pS2;
|
||||
char *zTitle = sqlite3_mprintf("Table %s and all its indexes", zUpper);
|
||||
char *zWhere = sqlite3_mprintf("tblname=%Q", zName);
|
||||
rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage);
|
||||
sqlite3_free(zTitle);
|
||||
sqlite3_free(zWhere);
|
||||
if( rc ) break;
|
||||
zTitle = sqlite3_mprintf("Table %s w/o any indexes", zUpper);
|
||||
zWhere = sqlite3_mprintf("name=%Q", zName);
|
||||
rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage);
|
||||
sqlite3_free(zTitle);
|
||||
sqlite3_free(zWhere);
|
||||
if( rc ) break;
|
||||
if( nSubIndex>1 ){
|
||||
zTitle = sqlite3_mprintf("All indexes of table %s", zUpper);
|
||||
zWhere = sqlite3_mprintf("tblname=%Q AND is_index", zName);
|
||||
rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage);
|
||||
sqlite3_free(zTitle);
|
||||
sqlite3_free(zWhere);
|
||||
if( rc ) break;
|
||||
}
|
||||
pS2 = analysisPrepare(&s,
|
||||
"SELECT name, upper(name) FROM temp.%s"
|
||||
" WHERE is_index AND tblname=%Q",
|
||||
s.zSU, zName);
|
||||
if( pS2==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
break;
|
||||
}
|
||||
while( (rc = sqlite3_step(pS2))==SQLITE_ROW ){
|
||||
const char *zU = (const char*)sqlite3_column_text(pS2, 1);
|
||||
const char *zN = (const char*)sqlite3_column_text(pS2, 0);
|
||||
zTitle = sqlite3_mprintf("Index %s", zU);
|
||||
zWhere = sqlite3_mprintf("name=%Q", zN);
|
||||
rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage);
|
||||
sqlite3_free(zTitle);
|
||||
sqlite3_free(zWhere);
|
||||
if( rc ) break;
|
||||
}
|
||||
rc = analysisStmtFinish(&s, rc, pS2);
|
||||
if( rc ) break;
|
||||
}
|
||||
}
|
||||
if( analysisStmtFinish(&s, rc, pStmt) ) return;
|
||||
|
||||
/* Append SQL statements that will recreate the raw data used for
|
||||
** the analysis.
|
||||
*/
|
||||
analysisTitle(&s, "Raw data used to generate this report");
|
||||
sqlite3_str_appendf(s.pOut,
|
||||
"The following SQL will create a table named \"space_used\" which\n"
|
||||
"contains most of the information used to generate the report above.\n"
|
||||
"*/\n"
|
||||
);
|
||||
sqlite3_str_appendf(s.pOut,
|
||||
"BEGIN;\n"
|
||||
"CREATE TABLE space_used(\n"
|
||||
" name text, -- A table or index\n" /* 0 */
|
||||
" tblname text, -- Table that owns name\n" /* 1 */
|
||||
" is_index boolean, -- TRUE if it is an index\n" /* 2 */
|
||||
" is_without_rowid boolean, -- TRUE if WITHOUT ROWID table\n" /* 3 */
|
||||
" nentry int, -- Number of entries in the BTree\n" /* 4 */
|
||||
" leaf_entries int, -- Number of leaf entries\n" /* 5 */
|
||||
" depth int, -- Depth of the b-tree\n" /* 6 */
|
||||
" payload int, -- Total data in this table/index\n" /* 7 */
|
||||
" ovfl_payload int, -- Total data on overflow pages\n" /* 8 */
|
||||
" ovfl_cnt int, -- Entries that use overflow\n" /* 9 */
|
||||
" mx_payload int, -- Maximum payload size\n" /* 10 */
|
||||
" int_pages int, -- Interior pages used\n" /* 11 */
|
||||
" leaf_pages int, -- Leaf pages used\n" /* 12 */
|
||||
" ovfl_pages int, -- Overflow pages used\n" /* 13 */
|
||||
" int_unused int, -- Unused bytes on interior pages\n" /* 14 */
|
||||
" leaf_unused int, -- Unused bytes on primary pages\n" /* 15 */
|
||||
" ovfl_unused int, -- Unused bytes on overflow pages\n" /* 16 */
|
||||
" int_entries int -- B-tree entries on internal pages\n"/* 17 */
|
||||
");\n"
|
||||
"INSERT INTO space_used VALUES\n"
|
||||
);
|
||||
pStmt = analysisPrepare(&s,
|
||||
"SELECT quote(name), quote(tblname),\n" /* 0..1 */
|
||||
" is_index, is_without_rowid, nentry, leaf_entries,\n" /* 2..5 */
|
||||
" depth, payload, ovfl_payload, ovfl_cnt, mx_payload,\n" /* 6..10 */
|
||||
" int_pages, leaf_pages, ovfl_pages, int_unused,\n" /* 11..14 */
|
||||
" leaf_unused, ovfl_unused, int_entries\n" /* 15..17 */
|
||||
" FROM temp.%s;",
|
||||
s.zSU);
|
||||
if( pStmt==0 ) return;
|
||||
n = 0;
|
||||
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
|
||||
if( n++ ) sqlite3_str_appendf(s.pOut,",\n");
|
||||
sqlite3_str_appendf(s.pOut,
|
||||
" (%s,%s,%lld,%lld,%lld,%lld,%lld,%lld,%lld,"
|
||||
"%lld,%lld,%lld,%lld,%lld,%lld,%lld,%lld,%lld)",
|
||||
sqlite3_column_text(pStmt, 0),
|
||||
sqlite3_column_text(pStmt, 1),
|
||||
sqlite3_column_int64(pStmt, 2),
|
||||
sqlite3_column_int64(pStmt, 3),
|
||||
sqlite3_column_int64(pStmt, 4),
|
||||
sqlite3_column_int64(pStmt, 5),
|
||||
sqlite3_column_int64(pStmt, 6),
|
||||
sqlite3_column_int64(pStmt, 7),
|
||||
sqlite3_column_int64(pStmt, 8),
|
||||
sqlite3_column_int64(pStmt, 9),
|
||||
sqlite3_column_int64(pStmt, 10),
|
||||
sqlite3_column_int64(pStmt, 11),
|
||||
sqlite3_column_int64(pStmt, 12),
|
||||
sqlite3_column_int64(pStmt, 13),
|
||||
sqlite3_column_int64(pStmt, 14),
|
||||
sqlite3_column_int64(pStmt, 15),
|
||||
sqlite3_column_int64(pStmt, 16),
|
||||
sqlite3_column_int64(pStmt, 17));
|
||||
}
|
||||
if( rc!=SQLITE_DONE ){
|
||||
analysisError(&s, "SQL run-time error: %s\nSQL: %s",
|
||||
sqlite3_errmsg(s.db), sqlite3_sql(pStmt));
|
||||
sqlite3_finalize(pStmt);
|
||||
return;
|
||||
}
|
||||
sqlite3_str_appendf(s.pOut,";\nCOMMIT;");
|
||||
sqlite3_finalize(pStmt);
|
||||
|
||||
if( sqlite3_str_length(s.pOut) ){
|
||||
sqlite3_result_text(context, sqlite3_str_finish(s.pOut), -1,
|
||||
sqlite3_free);
|
||||
s.pOut = 0;
|
||||
}
|
||||
analysisReset(&s);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_analyze_init(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
(void)pzErrMsg; /* Unused parameter */
|
||||
rc = sqlite3_create_function(db, "analyze", 1,
|
||||
SQLITE_UTF8|SQLITE_INNOCUOUS,
|
||||
0, analyzeFunc, 0, 0);
|
||||
return rc;
|
||||
}
|
||||
+3
-3
@@ -232,7 +232,7 @@ static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
sqlite3_result_text(context,"",-1,SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
cBuf = sqlite3_malloc(nc);
|
||||
cBuf = sqlite3_malloc64(nc);
|
||||
if( !cBuf ) goto memFail;
|
||||
nc = (int)(toBase64(bBuf, nb, cBuf) - cBuf);
|
||||
sqlite3_result_text(context, cBuf, nc, sqlite3_free);
|
||||
@@ -254,7 +254,7 @@ static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
sqlite3_result_zeroblob(context, 0);
|
||||
break;
|
||||
}
|
||||
bBuf = sqlite3_malloc(nb);
|
||||
bBuf = sqlite3_malloc64(nb);
|
||||
if( !bBuf ) goto memFail;
|
||||
nb = (int)(fromBase64(cBuf, nc, bBuf) - bBuf);
|
||||
sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
|
||||
@@ -275,7 +275,7 @@ static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_base_init
|
||||
int sqlite3_base64_init
|
||||
#else
|
||||
static int sqlite3_base64_init
|
||||
#endif
|
||||
|
||||
+9
-9
@@ -262,7 +262,7 @@ static int allBase85( char *p, int len ){
|
||||
|
||||
#ifndef BASE85_STANDALONE
|
||||
|
||||
# ifndef OMIT_BASE85_CHECKER
|
||||
#ifndef OMIT_BASE85_CHECKER
|
||||
/* This function does the work for the SQLite is_base85(t) UDF. */
|
||||
static void is_base85(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
assert(na==1);
|
||||
@@ -282,7 +282,7 @@ static void is_base85(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
return;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* This function does the work for the SQLite base85(x) UDF. */
|
||||
static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
@@ -309,7 +309,7 @@ static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
sqlite3_result_text(context,"",-1,SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
cBuf = sqlite3_malloc(nc);
|
||||
cBuf = sqlite3_malloc64(nc);
|
||||
if( !cBuf ) goto memFail;
|
||||
nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf);
|
||||
sqlite3_result_text(context, cBuf, nc, sqlite3_free);
|
||||
@@ -331,7 +331,7 @@ static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
sqlite3_result_zeroblob(context, 0);
|
||||
break;
|
||||
}
|
||||
bBuf = sqlite3_malloc(nb);
|
||||
bBuf = sqlite3_malloc64(nb);
|
||||
if( !bBuf ) goto memFail;
|
||||
nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf);
|
||||
sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
|
||||
@@ -352,14 +352,14 @@ static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_base_init
|
||||
int sqlite3_base85_init
|
||||
#else
|
||||
static int sqlite3_base85_init
|
||||
#endif
|
||||
(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
(void)pzErr;
|
||||
# ifndef OMIT_BASE85_CHECKER
|
||||
#ifndef OMIT_BASE85_CHECKER
|
||||
{
|
||||
int rc = sqlite3_create_function
|
||||
(db, "is_base85", 1,
|
||||
@@ -367,7 +367,7 @@ static int sqlite3_base85_init
|
||||
0, is_base85, 0, 0);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
return sqlite3_create_function
|
||||
(db, "base85", 1,
|
||||
SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8,
|
||||
@@ -432,9 +432,9 @@ int main(int na, char *av[]){
|
||||
int nc = strlen(cBuf);
|
||||
size_t nbo = fromBase85( cBuf, nc, bBuf ) - bBuf;
|
||||
if( 1 != fwrite(bBuf, nbo, 1, fb) ) rc = 1;
|
||||
# ifndef OMIT_BASE85_CHECKER
|
||||
#ifndef OMIT_BASE85_CHECKER
|
||||
b85Clean &= allBase85( cBuf, nc );
|
||||
# endif
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
+3
-3
@@ -516,7 +516,7 @@ static int closureConnect(
|
||||
|
||||
(void)pAux;
|
||||
*ppVtab = 0;
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
rc = SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -579,7 +579,7 @@ closureConnectError:
|
||||
static int closureOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
|
||||
closure_vtab *p = (closure_vtab*)pVTab;
|
||||
closure_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
pCur->pVtab = p;
|
||||
@@ -638,7 +638,7 @@ static int closureInsertNode(
|
||||
sqlite3_int64 id, /* The node ID */
|
||||
int iGeneration /* The generation number for this node */
|
||||
){
|
||||
closure_avl *pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
closure_avl *pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
pNew->id = id;
|
||||
|
||||
+27
-15
@@ -132,7 +132,7 @@ static int completionConnect(
|
||||
" phase INT HIDDEN" /* Used for debugging only */
|
||||
")");
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -154,7 +154,7 @@ static int completionDisconnect(sqlite3_vtab *pVtab){
|
||||
*/
|
||||
static int completionOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
completion_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
pCur->db = ((completion_vtab*)p)->db;
|
||||
@@ -199,6 +199,7 @@ static int completionNext(sqlite3_vtab_cursor *cur){
|
||||
completion_cursor *pCur = (completion_cursor*)cur;
|
||||
int eNextPhase = 0; /* Next phase to try if current phase reaches end */
|
||||
int iCol = -1; /* If >=0, step pCur->pStmt and use the i-th column */
|
||||
int rc;
|
||||
pCur->iRowid++;
|
||||
while( pCur->ePhase!=COMPLETION_EOF ){
|
||||
switch( pCur->ePhase ){
|
||||
@@ -224,22 +225,27 @@ static int completionNext(sqlite3_vtab_cursor *cur){
|
||||
case COMPLETION_TABLES: {
|
||||
if( pCur->pStmt==0 ){
|
||||
sqlite3_stmt *pS2;
|
||||
sqlite3_str* pStr = sqlite3_str_new(pCur->db);
|
||||
char *zSql = 0;
|
||||
const char *zSep = "";
|
||||
sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0);
|
||||
while( sqlite3_step(pS2)==SQLITE_ROW ){
|
||||
const char *zDb = (const char*)sqlite3_column_text(pS2, 1);
|
||||
zSql = sqlite3_mprintf(
|
||||
"%z%s"
|
||||
sqlite3_str_appendf(pStr,
|
||||
"%s"
|
||||
"SELECT name FROM \"%w\".sqlite_schema",
|
||||
zSql, zSep, zDb
|
||||
zSep, zDb
|
||||
);
|
||||
if( zSql==0 ) return SQLITE_NOMEM;
|
||||
zSep = " UNION ";
|
||||
}
|
||||
sqlite3_finalize(pS2);
|
||||
sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0);
|
||||
rc = sqlite3_finalize(pS2);
|
||||
zSql = sqlite3_str_finish(pStr);
|
||||
if( zSql==0 ) return SQLITE_NOMEM;
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0);
|
||||
}
|
||||
sqlite3_free(zSql);
|
||||
if( rc ) return rc;
|
||||
}
|
||||
iCol = 0;
|
||||
eNextPhase = COMPLETION_COLUMNS;
|
||||
@@ -248,24 +254,29 @@ static int completionNext(sqlite3_vtab_cursor *cur){
|
||||
case COMPLETION_COLUMNS: {
|
||||
if( pCur->pStmt==0 ){
|
||||
sqlite3_stmt *pS2;
|
||||
sqlite3_str *pStr = sqlite3_str_new(pCur->db);
|
||||
char *zSql = 0;
|
||||
const char *zSep = "";
|
||||
sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0);
|
||||
while( sqlite3_step(pS2)==SQLITE_ROW ){
|
||||
const char *zDb = (const char*)sqlite3_column_text(pS2, 1);
|
||||
zSql = sqlite3_mprintf(
|
||||
"%z%s"
|
||||
sqlite3_str_appendf(pStr,
|
||||
"%s"
|
||||
"SELECT pti.name FROM \"%w\".sqlite_schema AS sm"
|
||||
" JOIN pragma_table_xinfo(sm.name,%Q) AS pti"
|
||||
" WHERE sm.type='table'",
|
||||
zSql, zSep, zDb, zDb
|
||||
zSep, zDb, zDb
|
||||
);
|
||||
if( zSql==0 ) return SQLITE_NOMEM;
|
||||
zSep = " UNION ";
|
||||
}
|
||||
sqlite3_finalize(pS2);
|
||||
sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0);
|
||||
rc = sqlite3_finalize(pS2);
|
||||
zSql = sqlite3_str_finish(pStr);
|
||||
if( zSql==0 ) return SQLITE_NOMEM;
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0);
|
||||
}
|
||||
sqlite3_free(zSql);
|
||||
if( rc ) return rc;
|
||||
}
|
||||
iCol = 0;
|
||||
eNextPhase = COMPLETION_EOF;
|
||||
@@ -282,9 +293,10 @@ static int completionNext(sqlite3_vtab_cursor *cur){
|
||||
pCur->szRow = sqlite3_column_bytes(pCur->pStmt, iCol);
|
||||
}else{
|
||||
/* When all rows are finished, advance to the next phase */
|
||||
sqlite3_finalize(pCur->pStmt);
|
||||
rc = sqlite3_finalize(pCur->pStmt);
|
||||
pCur->pStmt = 0;
|
||||
pCur->ePhase = eNextPhase;
|
||||
if( rc ) return rc;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -64,6 +64,7 @@ SQLITE_EXTENSION_INIT1
|
||||
|
||||
#ifndef SQLITEINT_H
|
||||
typedef sqlite3_int64 i64;
|
||||
typedef sqlite3_uint64 u64;
|
||||
#endif
|
||||
|
||||
/* Max size of the error message in a CsvReader */
|
||||
@@ -128,7 +129,7 @@ static int csv_reader_open(
|
||||
const char *zData /* ... or use this data */
|
||||
){
|
||||
if( zFilename ){
|
||||
p->zIn = sqlite3_malloc( CSV_INBUFSZ );
|
||||
p->zIn = sqlite3_malloc64( CSV_INBUFSZ );
|
||||
if( p->zIn==0 ){
|
||||
csv_errmsg(p, "out of memory");
|
||||
return 1;
|
||||
@@ -221,7 +222,7 @@ static char *csv_read_one_field(CsvReader *p){
|
||||
}
|
||||
if( c=='"' ){
|
||||
int pc, ppc;
|
||||
int startLine = p->nLine;
|
||||
i64 startLine = p->nLine;
|
||||
pc = ppc = 0;
|
||||
while( 1 ){
|
||||
c = csv_getc(p);
|
||||
@@ -325,7 +326,7 @@ typedef struct CsvCursor {
|
||||
sqlite3_vtab_cursor base; /* Base class. Must be first */
|
||||
CsvReader rdr; /* The CsvReader object */
|
||||
char **azVal; /* Value of the current row */
|
||||
int *aLen; /* Length of each entry */
|
||||
i64 *aLen; /* Length of each entry */
|
||||
sqlite3_int64 iRowid; /* The current rowid. Negative for EOF */
|
||||
} CsvCursor;
|
||||
|
||||
@@ -497,7 +498,7 @@ static int csvtabConnect(
|
||||
CsvTable *pNew = 0; /* The CsvTable object to construct */
|
||||
int bHeader = -1; /* header= flags. -1 means not seen yet */
|
||||
int rc = SQLITE_OK; /* Result code from this routine */
|
||||
int i, j; /* Loop counters */
|
||||
u64 i, j; /* Loop counters */
|
||||
#ifdef SQLITE_TEST
|
||||
int tstFlags = 0; /* Value for testflags=N parameter */
|
||||
#endif
|
||||
@@ -516,7 +517,7 @@ static int csvtabConnect(
|
||||
assert( sizeof(azPValue)==sizeof(azParam) );
|
||||
memset(&sRdr, 0, sizeof(sRdr));
|
||||
memset(azPValue, 0, sizeof(azPValue));
|
||||
for(i=3; i<argc; i++){
|
||||
for(i=3; i<(u64)argc; i++){
|
||||
const char *z = argv[i];
|
||||
const char *zValue;
|
||||
for(j=0; j<sizeof(azParam)/sizeof(azParam[0]); j++){
|
||||
@@ -563,7 +564,7 @@ static int csvtabConnect(
|
||||
){
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) goto csvtab_connect_oom;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -709,12 +710,12 @@ static int csvtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
CsvTable *pTab = (CsvTable*)p;
|
||||
CsvCursor *pCur;
|
||||
size_t nByte;
|
||||
nByte = sizeof(*pCur) + (sizeof(char*)+sizeof(int))*pTab->nCol;
|
||||
nByte = sizeof(*pCur) + (sizeof(char*)+sizeof(i64))*pTab->nCol;
|
||||
pCur = sqlite3_malloc64( nByte );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, nByte);
|
||||
pCur->azVal = (char**)&pCur[1];
|
||||
pCur->aLen = (int*)&pCur->azVal[pTab->nCol];
|
||||
pCur->aLen = (i64*)&pCur->azVal[pTab->nCol];
|
||||
*ppCursor = &pCur->base;
|
||||
if( csv_reader_open(&pCur->rdr, pTab->zFilename, pTab->zData) ){
|
||||
csv_xfer_error(pTab, &pCur->rdr);
|
||||
|
||||
+65
-15
@@ -31,6 +31,10 @@ SQLITE_EXTENSION_INIT1
|
||||
#define IsSpace(X) isspace((unsigned char)X)
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_DECIMAL_MAX_DIGIT
|
||||
# define SQLITE_DECIMAL_MAX_DIGIT 10000000
|
||||
#endif
|
||||
|
||||
/* A decimal object */
|
||||
typedef struct Decimal Decimal;
|
||||
struct Decimal {
|
||||
@@ -69,7 +73,8 @@ static Decimal *decimalNewFromText(const char *zIn, int n){
|
||||
int i;
|
||||
int iExp = 0;
|
||||
|
||||
p = sqlite3_malloc( sizeof(*p) );
|
||||
if( zIn==0 ) goto new_from_text_failed;
|
||||
p = sqlite3_malloc64( sizeof(*p) );
|
||||
if( p==0 ) goto new_from_text_failed;
|
||||
p->sign = 0;
|
||||
p->oom = 0;
|
||||
@@ -128,9 +133,10 @@ static Decimal *decimalNewFromText(const char *zIn, int n){
|
||||
}
|
||||
}
|
||||
if( iExp>0 ){
|
||||
p->a = sqlite3_realloc64(p->a, (sqlite3_int64)p->nDigit
|
||||
signed char *a = sqlite3_realloc64(p->a, (sqlite3_int64)p->nDigit
|
||||
+ (sqlite3_int64)iExp + 1 );
|
||||
if( p->a==0 ) goto new_from_text_failed;
|
||||
if( a==0 ) goto new_from_text_failed;
|
||||
p->a = a;
|
||||
memset(p->a+p->nDigit, 0, iExp);
|
||||
p->nDigit += iExp;
|
||||
}
|
||||
@@ -148,9 +154,10 @@ static Decimal *decimalNewFromText(const char *zIn, int n){
|
||||
}
|
||||
}
|
||||
if( iExp>0 ){
|
||||
p->a = sqlite3_realloc64(p->a, (sqlite3_int64)p->nDigit
|
||||
signed char *a = sqlite3_realloc64(p->a, (sqlite3_int64)p->nDigit
|
||||
+ (sqlite3_int64)iExp + 1 );
|
||||
if( p->a==0 ) goto new_from_text_failed;
|
||||
if( a==0 ) goto new_from_text_failed;
|
||||
p->a = a;
|
||||
memmove(p->a+iExp, p->a, p->nDigit);
|
||||
memset(p->a, 0, iExp);
|
||||
p->nDigit += iExp;
|
||||
@@ -161,6 +168,7 @@ static Decimal *decimalNewFromText(const char *zIn, int n){
|
||||
for(i=0; i<p->nDigit && p->a[i]==0; i++){}
|
||||
if( i>=p->nDigit ) p->sign = 0;
|
||||
}
|
||||
if( p->nDigit>SQLITE_DECIMAL_MAX_DIGIT ) goto new_from_text_failed;
|
||||
return p;
|
||||
|
||||
new_from_text_failed:
|
||||
@@ -291,12 +299,38 @@ static void decimal_result(sqlite3_context *pCtx, Decimal *p){
|
||||
sqlite3_result_text(pCtx, z, i, sqlite3_free);
|
||||
}
|
||||
|
||||
/*
|
||||
** Round a decimal value to N significant digits. N must be positive.
|
||||
*/
|
||||
static void decimal_round(Decimal *p, int N){
|
||||
int i;
|
||||
int nZero;
|
||||
if( N<1 ) return;
|
||||
if( p==0 ) return;
|
||||
if( p->nDigit<=N ) return;
|
||||
for(nZero=0; nZero<p->nDigit && p->a[nZero]==0; nZero++){}
|
||||
N += nZero;
|
||||
if( p->nDigit<=N ) return;
|
||||
if( p->a[N]>4 ){
|
||||
p->a[N-1]++;
|
||||
for(i=N-1; i>0 && p->a[i]>9; i--){
|
||||
p->a[i] = 0;
|
||||
p->a[i-1]++;
|
||||
}
|
||||
if( p->a[0]>9 ){
|
||||
p->a[0] = 1;
|
||||
p->nFrac--;
|
||||
}
|
||||
}
|
||||
memset(&p->a[N], 0, p->nDigit - N);
|
||||
}
|
||||
|
||||
/*
|
||||
** Make the given Decimal the result in an format similar to '%+#e'.
|
||||
** In other words, show exponential notation with leading and trailing
|
||||
** zeros omitted.
|
||||
*/
|
||||
static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p){
|
||||
static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p, int N){
|
||||
char *z; /* The output buffer */
|
||||
int i; /* Loop counter */
|
||||
int nZero; /* Number of leading zeros */
|
||||
@@ -314,7 +348,8 @@ static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p){
|
||||
sqlite3_result_null(pCtx);
|
||||
return;
|
||||
}
|
||||
for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){}
|
||||
if( N<1 ) N = 0;
|
||||
for(nDigit=p->nDigit; nDigit>N && p->a[nDigit-1]==0; nDigit--){}
|
||||
for(nZero=0; nZero<nDigit && p->a[nZero]==0; nZero++){}
|
||||
nFrac = p->nFrac + (nDigit - p->nDigit);
|
||||
nDigit -= nZero;
|
||||
@@ -430,15 +465,18 @@ cmp_done:
|
||||
static void decimal_expand(Decimal *p, int nDigit, int nFrac){
|
||||
int nAddSig;
|
||||
int nAddFrac;
|
||||
signed char *a;
|
||||
if( p==0 ) return;
|
||||
nAddFrac = nFrac - p->nFrac;
|
||||
nAddSig = (nDigit - p->nDigit) - nAddFrac;
|
||||
if( nAddFrac==0 && nAddSig==0 ) return;
|
||||
p->a = sqlite3_realloc64(p->a, nDigit+1);
|
||||
if( p->a==0 ){
|
||||
if( nDigit+1>SQLITE_DECIMAL_MAX_DIGIT ){ p->oom = 1; return; }
|
||||
a = sqlite3_realloc64(p->a, nDigit+1);
|
||||
if( a==0 ){
|
||||
p->oom = 1;
|
||||
return;
|
||||
}
|
||||
p->a = a;
|
||||
if( nAddSig ){
|
||||
memmove(p->a+nAddSig, p->a, p->nDigit);
|
||||
memset(p->a, 0, nAddSig);
|
||||
@@ -533,14 +571,18 @@ static void decimalMul(Decimal *pA, Decimal *pB){
|
||||
signed char *acc = 0;
|
||||
int i, j, k;
|
||||
int minFrac;
|
||||
sqlite3_int64 sumDigit;
|
||||
|
||||
if( pA==0 || pA->oom || pA->isNull
|
||||
|| pB==0 || pB->oom || pB->isNull
|
||||
){
|
||||
goto mul_end;
|
||||
}
|
||||
acc = sqlite3_malloc64( (sqlite3_int64)pA->nDigit +
|
||||
(sqlite3_int64)pB->nDigit + 2 );
|
||||
sumDigit = pA->nDigit;
|
||||
sumDigit += pB->nDigit;
|
||||
sumDigit += 2;
|
||||
if( sumDigit>SQLITE_DECIMAL_MAX_DIGIT ){ pA->oom = 1; return; }
|
||||
acc = sqlite3_malloc64( sumDigit );
|
||||
if( acc==0 ){
|
||||
pA->oom = 1;
|
||||
goto mul_end;
|
||||
@@ -677,10 +719,16 @@ static void decimalFunc(
|
||||
sqlite3_value **argv
|
||||
){
|
||||
Decimal *p = decimal_new(context, argv[0], 0);
|
||||
UNUSED_PARAMETER(argc);
|
||||
int N;
|
||||
if( argc==2 ){
|
||||
N = sqlite3_value_int(argv[1]);
|
||||
if( N>0 ) decimal_round(p, N);
|
||||
}else{
|
||||
N = 0;
|
||||
}
|
||||
if( p ){
|
||||
if( sqlite3_user_data(context)!=0 ){
|
||||
decimal_result_sci(context, p);
|
||||
decimal_result_sci(context, p, N);
|
||||
}else{
|
||||
decimal_result(context, p);
|
||||
}
|
||||
@@ -766,7 +814,7 @@ static void decimalSumStep(
|
||||
if( p==0 ) return;
|
||||
if( !p->isInit ){
|
||||
p->isInit = 1;
|
||||
p->a = sqlite3_malloc(2);
|
||||
p->a = sqlite3_malloc64(2);
|
||||
if( p->a==0 ){
|
||||
p->oom = 1;
|
||||
}else{
|
||||
@@ -850,7 +898,7 @@ static void decimalPow2Func(
|
||||
UNUSED_PARAMETER(argc);
|
||||
if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){
|
||||
Decimal *pA = decimalPow2(sqlite3_value_int(argv[0]));
|
||||
decimal_result_sci(context, pA);
|
||||
decimal_result_sci(context, pA, 0);
|
||||
decimal_free(pA);
|
||||
}
|
||||
}
|
||||
@@ -871,7 +919,9 @@ int sqlite3_decimal_init(
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
|
||||
} aFunc[] = {
|
||||
{ "decimal", 1, 0, decimalFunc },
|
||||
{ "decimal", 2, 0, decimalFunc },
|
||||
{ "decimal_exp", 1, 1, decimalFunc },
|
||||
{ "decimal_exp", 2, 1, decimalFunc },
|
||||
{ "decimal_cmp", 2, 0, decimalCmpFunc },
|
||||
{ "decimal_add", 2, 0, decimalAddFunc },
|
||||
{ "decimal_sub", 2, 0, decimalSubFunc },
|
||||
|
||||
+2
-2
@@ -92,7 +92,7 @@ static int explainConnect(
|
||||
rc = sqlite3_declare_vtab(db,
|
||||
"CREATE TABLE x(addr,opcode,p1,p2,p3,p4,p5,comment,sql HIDDEN)");
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -114,7 +114,7 @@ static int explainDisconnect(sqlite3_vtab *pVtab){
|
||||
*/
|
||||
static int explainOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
explain_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
pCur->db = ((explain_vtab*)p)->db;
|
||||
|
||||
+175
-64
@@ -73,11 +73,6 @@
|
||||
** $path is a relative path, then $path is interpreted relative to $dir.
|
||||
** And the paths returned in the "name" column of the table are also
|
||||
** relative to directory $dir.
|
||||
**
|
||||
** Notes on building this extension for Windows:
|
||||
** Unless linked statically with the SQLite library, a preprocessor
|
||||
** symbol, FILEIO_WIN32_DLL, must be #define'd to create a stand-alone
|
||||
** DLL form of this extension for WIN32. See its use below for details.
|
||||
*/
|
||||
#include "sqlite3ext.h"
|
||||
SQLITE_EXTENSION_INIT1
|
||||
@@ -94,12 +89,16 @@ SQLITE_EXTENSION_INIT1
|
||||
# include <utime.h>
|
||||
# include <sys/time.h>
|
||||
# define STRUCT_STAT struct stat
|
||||
# include <limits.h>
|
||||
# include <stdlib.h>
|
||||
#else
|
||||
# include "windirent.h"
|
||||
# include <direct.h>
|
||||
# define STRUCT_STAT struct _stat
|
||||
# define chmod(path,mode) fileio_chmod(path,mode)
|
||||
# define mkdir(path,mode) fileio_mkdir(path)
|
||||
extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
|
||||
extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR);
|
||||
#endif
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
@@ -131,12 +130,9 @@ SQLITE_EXTENSION_INIT1
|
||||
*/
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
static int fileio_chmod(const char *zPath, int pmode){
|
||||
sqlite3_int64 sz = strlen(zPath);
|
||||
wchar_t *b1 = sqlite3_malloc64( (sz+1)*sizeof(b1[0]) );
|
||||
int rc;
|
||||
wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath);
|
||||
if( b1==0 ) return -1;
|
||||
sz = MultiByteToWideChar(CP_UTF8, 0, zPath, sz, b1, sz);
|
||||
b1[sz] = 0;
|
||||
rc = _wchmod(b1, pmode);
|
||||
sqlite3_free(b1);
|
||||
return rc;
|
||||
@@ -148,12 +144,9 @@ static int fileio_chmod(const char *zPath, int pmode){
|
||||
*/
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
static int fileio_mkdir(const char *zPath){
|
||||
sqlite3_int64 sz = strlen(zPath);
|
||||
wchar_t *b1 = sqlite3_malloc64( (sz+1)*sizeof(b1[0]) );
|
||||
int rc;
|
||||
wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath);
|
||||
if( b1==0 ) return -1;
|
||||
sz = MultiByteToWideChar(CP_UTF8, 0, zPath, sz, b1, sz);
|
||||
b1[sz] = 0;
|
||||
rc = _wmkdir(b1);
|
||||
sqlite3_free(b1);
|
||||
return rc;
|
||||
@@ -266,50 +259,7 @@ static sqlite3_uint64 fileTimeToUnixTime(
|
||||
|
||||
return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000;
|
||||
}
|
||||
|
||||
|
||||
#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32))
|
||||
# /* To allow a standalone DLL, use this next replacement function: */
|
||||
# undef sqlite3_win32_utf8_to_unicode
|
||||
# define sqlite3_win32_utf8_to_unicode utf8_to_utf16
|
||||
#
|
||||
LPWSTR utf8_to_utf16(const char *z){
|
||||
int nAllot = MultiByteToWideChar(CP_UTF8, 0, z, -1, NULL, 0);
|
||||
LPWSTR rv = sqlite3_malloc(nAllot * sizeof(WCHAR));
|
||||
if( rv!=0 && 0 < MultiByteToWideChar(CP_UTF8, 0, z, -1, rv, nAllot) )
|
||||
return rv;
|
||||
sqlite3_free(rv);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** This function attempts to normalize the time values found in the stat()
|
||||
** buffer to UTC. This is necessary on Win32, where the runtime library
|
||||
** appears to return these values as local times.
|
||||
*/
|
||||
static void statTimesToUtc(
|
||||
const char *zPath,
|
||||
STRUCT_STAT *pStatBuf
|
||||
){
|
||||
HANDLE hFindFile;
|
||||
WIN32_FIND_DATAW fd;
|
||||
LPWSTR zUnicodeName;
|
||||
extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
|
||||
zUnicodeName = sqlite3_win32_utf8_to_unicode(zPath);
|
||||
if( zUnicodeName ){
|
||||
memset(&fd, 0, sizeof(WIN32_FIND_DATAW));
|
||||
hFindFile = FindFirstFileW(zUnicodeName, &fd);
|
||||
if( hFindFile!=NULL ){
|
||||
pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime);
|
||||
pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime);
|
||||
pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime);
|
||||
FindClose(hFindFile);
|
||||
}
|
||||
sqlite3_free(zUnicodeName);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif /* _WIN32 */
|
||||
|
||||
/*
|
||||
** This function is used in place of stat(). On Windows, special handling
|
||||
@@ -321,14 +271,22 @@ static int fileStat(
|
||||
STRUCT_STAT *pStatBuf
|
||||
){
|
||||
#if defined(_WIN32)
|
||||
sqlite3_int64 sz = strlen(zPath);
|
||||
wchar_t *b1 = sqlite3_malloc64( (sz+1)*sizeof(b1[0]) );
|
||||
int rc;
|
||||
wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath);
|
||||
if( b1==0 ) return 1;
|
||||
sz = MultiByteToWideChar(CP_UTF8, 0, zPath, sz, b1, sz);
|
||||
b1[sz] = 0;
|
||||
rc = _wstat(b1, pStatBuf);
|
||||
if( rc==0 ) statTimesToUtc(zPath, pStatBuf);
|
||||
if( rc==0 ){
|
||||
HANDLE hFindFile;
|
||||
WIN32_FIND_DATAW fd;
|
||||
memset(&fd, 0, sizeof(WIN32_FIND_DATAW));
|
||||
hFindFile = FindFirstFileW(b1, &fd);
|
||||
if( hFindFile!=NULL ){
|
||||
pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime);
|
||||
pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime);
|
||||
pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime);
|
||||
FindClose(hFindFile);
|
||||
}
|
||||
}
|
||||
sqlite3_free(b1);
|
||||
return rc;
|
||||
#else
|
||||
@@ -655,7 +613,7 @@ static int fsdirConnect(
|
||||
(void)pzErr;
|
||||
rc = sqlite3_declare_vtab(db, "CREATE TABLE x" FSDIR_SCHEMA);
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = (fsdir_tab*)sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = (fsdir_tab*)sqlite3_malloc64( sizeof(*pNew) );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY);
|
||||
@@ -678,7 +636,7 @@ static int fsdirDisconnect(sqlite3_vtab *pVtab){
|
||||
static int fsdirOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
fsdir_cursor *pCur;
|
||||
(void)p;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
pCur->iLvl = -1;
|
||||
@@ -1093,6 +1051,154 @@ static int fsdirRegister(sqlite3 *db){
|
||||
# define fsdirRegister(x) SQLITE_OK
|
||||
#endif
|
||||
|
||||
/*
|
||||
** This version of realpath() works on any system. The string
|
||||
** returned is held in memory allocated using sqlite3_malloc64().
|
||||
** The caller is responsible for calling sqlite3_free().
|
||||
*/
|
||||
static char *portable_realpath(const char *zPath){
|
||||
#if !defined(_WIN32) /* BEGIN unix */
|
||||
|
||||
char *zOut = 0; /* Result */
|
||||
char *z; /* Temporary buffer */
|
||||
#if defined(PATH_MAX)
|
||||
char zBuf[PATH_MAX+1]; /* Space for the temporary buffer */
|
||||
#endif
|
||||
|
||||
if( zPath==0 ) return 0;
|
||||
#if defined(PATH_MAX)
|
||||
z = realpath(zPath, zBuf);
|
||||
if( z ){
|
||||
zOut = sqlite3_mprintf("%s", zBuf);
|
||||
}
|
||||
#endif /* defined(PATH_MAX) */
|
||||
if( zOut==0 ){
|
||||
/* Try POSIX.1-2008 malloc behavior */
|
||||
z = realpath(zPath, NULL);
|
||||
if( z ){
|
||||
zOut = sqlite3_mprintf("%s", z);
|
||||
free(z);
|
||||
}
|
||||
}
|
||||
return zOut;
|
||||
|
||||
#else /* End UNIX, Begin WINDOWS */
|
||||
|
||||
wchar_t *zPath16; /* UTF16 translation of zPath */
|
||||
char *zOut = 0; /* Result */
|
||||
wchar_t *z = 0; /* Temporary buffer */
|
||||
|
||||
if( zPath==0 ) return 0;
|
||||
|
||||
zPath16 = sqlite3_win32_utf8_to_unicode(zPath);
|
||||
if( zPath16==0 ) return 0;
|
||||
z = _wfullpath(NULL, zPath16, 0);
|
||||
sqlite3_free(zPath16);
|
||||
if( z ){
|
||||
zOut = sqlite3_win32_unicode_to_utf8(z);
|
||||
free(z);
|
||||
}
|
||||
return zOut;
|
||||
|
||||
#endif /* End WINDOWS, Begin common code */
|
||||
}
|
||||
|
||||
/*
|
||||
** SQL function: realpath(X)
|
||||
**
|
||||
** Try to convert file or pathname X into its real, absolute pathname.
|
||||
** Return NULL if unable.
|
||||
**
|
||||
** The file or directory X is not required to exist. The answer is formed
|
||||
** by calling system realpath() on the prefix of X that does exist and
|
||||
** appending the tail of X that does not (yet) exist.
|
||||
*/
|
||||
static void realpathFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
const char *zPath; /* Original input path */
|
||||
char *zCopy; /* An editable copy of zPath */
|
||||
char *zOut; /* The result */
|
||||
char cSep = 0; /* Separator turned into \000 */
|
||||
size_t len; /* Prefix length before cSep */
|
||||
#ifdef _WIN32
|
||||
const int isWin = 1;
|
||||
#else
|
||||
const int isWin = 0;
|
||||
#endif
|
||||
|
||||
(void)argc;
|
||||
zPath = (const char*)sqlite3_value_text(argv[0]);
|
||||
if( zPath==0 ) return;
|
||||
if( zPath[0]==0 ) zPath = ".";
|
||||
zCopy = sqlite3_mprintf("%s",zPath);
|
||||
len = strlen(zCopy);
|
||||
while( len>1 && (zCopy[len-1]=='/' || (isWin && zCopy[len-1]=='\\')) ){
|
||||
len--;
|
||||
}
|
||||
zCopy[len] = 0;
|
||||
while( 1 /*exit-by-break*/ ){
|
||||
zOut = portable_realpath(zCopy);
|
||||
zCopy[len] = cSep;
|
||||
if( zOut ){
|
||||
if( cSep ){
|
||||
zOut = sqlite3_mprintf("%z%s",zOut,&zCopy[len]);
|
||||
}
|
||||
break;
|
||||
}else{
|
||||
size_t i = len-1;
|
||||
while( i>0 ){
|
||||
if( zCopy[i]=='/' || (isWin && zCopy[i]=='\\') ) break;
|
||||
i--;
|
||||
}
|
||||
if( i<=0 ){
|
||||
if( zCopy[0]=='/' ){
|
||||
zOut = zCopy;
|
||||
zCopy = 0;
|
||||
}else if( (zOut = portable_realpath("."))!=0 ){
|
||||
zOut = sqlite3_mprintf("%z/%s", zOut, zCopy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
cSep = zCopy[i];
|
||||
zCopy[i] = 0;
|
||||
len = i;
|
||||
}
|
||||
}
|
||||
sqlite3_free(zCopy);
|
||||
if( zOut ){
|
||||
/* Simplify any "/./" or "/../" that might have snuck into the
|
||||
** pathname due to appending of zCopy. We only have to consider
|
||||
** unix "/" separators, because the _wfilepath() system call on
|
||||
** Windows will have already done this simplification for us. */
|
||||
size_t i, j, n;
|
||||
n = strlen(zOut);
|
||||
for(i=j=0; i<n; i++){
|
||||
if( zOut[i]=='/' ){
|
||||
if( zOut[i+1]=='/' ) continue;
|
||||
if( zOut[i+1]=='.' && i+2<n && zOut[i+2]=='/' ){
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if( zOut[i+1]=='.' && i+3<n && zOut[i+2]=='.' && zOut[i+3]=='/' ){
|
||||
while( j>0 && zOut[j-1]!='/' ){ j--; }
|
||||
if( j>0 ){ j--; }
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
zOut[j++] = zOut[i];
|
||||
}
|
||||
zOut[j] = 0;
|
||||
|
||||
/* Return the result */
|
||||
sqlite3_result_text(context, zOut, -1, sqlite3_free);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
@@ -1119,5 +1225,10 @@ int sqlite3_fileio_init(
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fsdirRegister(db);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_create_function(db, "realpath", 1,
|
||||
SQLITE_UTF8, 0,
|
||||
realpathFunc, 0, 0);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -843,7 +843,7 @@ static int deltaparsevtabDisconnect(sqlite3_vtab *pVtab){
|
||||
*/
|
||||
static int deltaparsevtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
deltaparsevtab_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
*ppCursor = &pCur->base;
|
||||
|
||||
+3
-3
@@ -556,7 +556,7 @@ static int fuzzerConnect(
|
||||
static int fuzzerOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
|
||||
fuzzer_vtab *p = (fuzzer_vtab*)pVTab;
|
||||
fuzzer_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
pCur->pVtab = p;
|
||||
@@ -617,12 +617,12 @@ static int fuzzerRender(
|
||||
int *pnBuf /* Size of the buffer */
|
||||
){
|
||||
const fuzzer_rule *pRule = pStem->pRule;
|
||||
int n; /* Size of output term without nul-term */
|
||||
sqlite3_int64 n; /* Size of output term without nul-term */
|
||||
char *z; /* Buffer to assemble output term in */
|
||||
|
||||
n = pStem->nBasis + pRule->nTo - pRule->nFrom;
|
||||
if( (*pnBuf)<n+1 ){
|
||||
(*pzBuf) = sqlite3_realloc((*pzBuf), n+100);
|
||||
(*pzBuf) = sqlite3_realloc64((*pzBuf), n+100);
|
||||
if( (*pzBuf)==0 ) return SQLITE_NOMEM;
|
||||
(*pnBuf) = n+100;
|
||||
}
|
||||
|
||||
@@ -259,6 +259,38 @@ static void ieee754func_to_blob(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Functions to convert between 64-bit integers and floats.
|
||||
**
|
||||
** The bit patterns are copied. The numeric values are different.
|
||||
*/
|
||||
static void ieee754func_from_int(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
UNUSED_PARAMETER(argc);
|
||||
if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){
|
||||
double r;
|
||||
sqlite3_int64 v = sqlite3_value_int64(argv[0]);
|
||||
memcpy(&r, &v, sizeof(r));
|
||||
sqlite3_result_double(context, r);
|
||||
}
|
||||
}
|
||||
static void ieee754func_to_int(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
UNUSED_PARAMETER(argc);
|
||||
if( sqlite3_value_type(argv[0])==SQLITE_FLOAT ){
|
||||
double r = sqlite3_value_double(argv[0]);
|
||||
sqlite3_uint64 v;
|
||||
memcpy(&v, &r, sizeof(v));
|
||||
sqlite3_result_int64(context, v);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** SQL Function: ieee754_inc(r,N)
|
||||
**
|
||||
@@ -311,6 +343,8 @@ int sqlite3_ieee_init(
|
||||
{ "ieee754_exponent", 1, 2, ieee754func },
|
||||
{ "ieee754_to_blob", 1, 0, ieee754func_to_blob },
|
||||
{ "ieee754_from_blob", 1, 0, ieee754func_from_blob },
|
||||
{ "ieee754_to_int", 1, 0, ieee754func_to_int },
|
||||
{ "ieee754_from_int", 1, 0, ieee754func_from_int },
|
||||
{ "ieee754_inc", 2, 0, ieee754inc },
|
||||
};
|
||||
unsigned int i;
|
||||
|
||||
+2
-2
@@ -85,7 +85,7 @@ static int memstatConnect(
|
||||
|
||||
rc = sqlite3_declare_vtab(db,"CREATE TABLE x(name,schema,value,hiwtr)");
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -107,7 +107,7 @@ static int memstatDisconnect(sqlite3_vtab *pVtab){
|
||||
*/
|
||||
static int memstatOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
memstat_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
pCur->db = ((memstat_vtab*)p)->db;
|
||||
|
||||
+2
-2
@@ -75,7 +75,7 @@ static int prefixesConnect(
|
||||
"CREATE TABLE prefixes(prefix TEXT, original_string TEXT HIDDEN)"
|
||||
);
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -98,7 +98,7 @@ static int prefixesDisconnect(sqlite3_vtab *pVtab){
|
||||
*/
|
||||
static int prefixesOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
prefixes_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
*ppCursor = &pCur->base;
|
||||
|
||||
+2
-2
@@ -152,7 +152,7 @@ static int qpvtabConnect(
|
||||
#define QPVTAB_FLAGS 11
|
||||
#define QPVTAB_NONE 12
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -174,7 +174,7 @@ static int qpvtabDisconnect(sqlite3_vtab *pVtab){
|
||||
*/
|
||||
static int qpvtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
qpvtab_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
*ppCursor = &pCur->base;
|
||||
|
||||
+2
-2
@@ -676,7 +676,7 @@ static const char *re_compile(
|
||||
int i, j;
|
||||
|
||||
*ppRe = 0;
|
||||
pRe = sqlite3_malloc( sizeof(*pRe) );
|
||||
pRe = sqlite3_malloc64( sizeof(*pRe) );
|
||||
if( pRe==0 ){
|
||||
return "out of memory";
|
||||
}
|
||||
@@ -828,7 +828,6 @@ static void re_bytecode_func(
|
||||
int i;
|
||||
int n;
|
||||
char *z;
|
||||
(void)argc;
|
||||
static const char *ReOpName[] = {
|
||||
"EOF",
|
||||
"MATCH",
|
||||
@@ -851,6 +850,7 @@ static void re_bytecode_func(
|
||||
"ATSTART",
|
||||
};
|
||||
|
||||
(void)argc;
|
||||
zPattern = (const char*)sqlite3_value_text(argv[0]);
|
||||
if( zPattern==0 ) return;
|
||||
zErr = re_compile(&pRe, zPattern, re_maxnfa(re_maxlen(context)),
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ static void scrubBackupErr(ScrubState *p, const char *zFormat, ...){
|
||||
static u8 *scrubBackupAllocPage(ScrubState *p){
|
||||
u8 *pPage;
|
||||
if( p->rcErr ) return 0;
|
||||
pPage = sqlite3_malloc( p->szPage );
|
||||
pPage = sqlite3_malloc64( p->szPage );
|
||||
if( pPage==0 ) p->rcErr = SQLITE_NOMEM;
|
||||
return pPage;
|
||||
}
|
||||
|
||||
+2
-2
@@ -239,7 +239,7 @@ static int seriesConnect(
|
||||
rc = sqlite3_declare_vtab(db,
|
||||
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)");
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = *ppVtab = sqlite3_malloc64( sizeof(*pNew) );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
|
||||
@@ -261,7 +261,7 @@ static int seriesDisconnect(sqlite3_vtab *pVtab){
|
||||
static int seriesOpen(sqlite3_vtab *pUnused, sqlite3_vtab_cursor **ppCursor){
|
||||
series_cursor *pCur;
|
||||
(void)pUnused;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
*ppCursor = &pCur->base;
|
||||
|
||||
+6
-2
@@ -249,16 +249,19 @@ static void sha1Func(
|
||||
SHA1Context cx;
|
||||
int eType = sqlite3_value_type(argv[0]);
|
||||
int nByte = sqlite3_value_bytes(argv[0]);
|
||||
const unsigned char *pData;
|
||||
char zOut[44];
|
||||
|
||||
assert( argc==1 );
|
||||
if( eType==SQLITE_NULL ) return;
|
||||
hash_init(&cx);
|
||||
if( eType==SQLITE_BLOB ){
|
||||
hash_step(&cx, sqlite3_value_blob(argv[0]), nByte);
|
||||
pData = (const unsigned char*)sqlite3_value_blob(argv[0]);
|
||||
}else{
|
||||
hash_step(&cx, sqlite3_value_text(argv[0]), nByte);
|
||||
pData = (const unsigned char*)sqlite3_value_text(argv[0]);
|
||||
}
|
||||
if( pData==0 ) return;
|
||||
hash_step(&cx, pData, nByte);
|
||||
if( sqlite3_user_data(context)!=0 ){
|
||||
/* sha1b() - binary result */
|
||||
hash_finish(&cx, zOut, 1);
|
||||
@@ -320,6 +323,7 @@ static void sha1QueryFunc(
|
||||
}
|
||||
nCol = sqlite3_column_count(pStmt);
|
||||
z = sqlite3_sql(pStmt);
|
||||
if( z==0 ) z = "";
|
||||
n = (int)strlen(z);
|
||||
hash_step_vformat(&cx,"S%d:",n);
|
||||
hash_step(&cx,(unsigned char*)z,n);
|
||||
|
||||
+3
-3
@@ -46,7 +46,7 @@ static void sqlarCompressFunc(
|
||||
uLongf nOut = compressBound(nData);
|
||||
Bytef *pOut;
|
||||
|
||||
pOut = (Bytef*)sqlite3_malloc(nOut);
|
||||
pOut = (Bytef*)sqlite3_malloc64(nOut);
|
||||
if( pOut==0 ){
|
||||
sqlite3_result_error_nomem(context);
|
||||
return;
|
||||
@@ -84,14 +84,14 @@ static void sqlarUncompressFunc(
|
||||
sqlite3_int64 sz;
|
||||
|
||||
assert( argc==2 );
|
||||
sz = sqlite3_value_int(argv[1]);
|
||||
sz = sqlite3_value_int64(argv[1]);
|
||||
|
||||
if( sz<=0 || sz==(nData = sqlite3_value_bytes(argv[0])) ){
|
||||
sqlite3_result_value(context, argv[0]);
|
||||
}else{
|
||||
uLongf szf = sz;
|
||||
const Bytef *pData= sqlite3_value_blob(argv[0]);
|
||||
Bytef *pOut = sqlite3_malloc(sz);
|
||||
Bytef *pOut = sqlite3_malloc64(sz);
|
||||
if( pOut==0 ){
|
||||
sqlite3_result_error_nomem(context);
|
||||
}else if( Z_OK!=uncompress(pOut, &szf, pData, nData) ){
|
||||
|
||||
@@ -101,8 +101,8 @@ FILE *sqlite3_fopen(const char *zFilename, const char *zMode){
|
||||
|
||||
sz1 = (int)strlen(zFilename);
|
||||
sz2 = (int)strlen(zMode);
|
||||
b1 = sqlite3_malloc( (sz1+1)*sizeof(b1[0]) );
|
||||
b2 = sqlite3_malloc( (sz2+1)*sizeof(b1[0]) );
|
||||
b1 = sqlite3_malloc64( (sz1+1)*sizeof(b1[0]) );
|
||||
b2 = sqlite3_malloc64( (sz2+1)*sizeof(b1[0]) );
|
||||
if( b1 && b2 ){
|
||||
sz1 = MultiByteToWideChar(CP_UTF8, 0, zFilename, sz1, b1, sz1);
|
||||
b1[sz1] = 0;
|
||||
@@ -127,8 +127,8 @@ FILE *sqlite3_popen(const char *zCommand, const char *zMode){
|
||||
|
||||
sz1 = (int)strlen(zCommand);
|
||||
sz2 = (int)strlen(zMode);
|
||||
b1 = sqlite3_malloc( (sz1+1)*sizeof(b1[0]) );
|
||||
b2 = sqlite3_malloc( (sz2+1)*sizeof(b1[0]) );
|
||||
b1 = sqlite3_malloc64( (sz1+1)*sizeof(b1[0]) );
|
||||
b2 = sqlite3_malloc64( (sz2+1)*sizeof(b1[0]) );
|
||||
if( b1 && b2 ){
|
||||
sz1 = MultiByteToWideChar(CP_UTF8, 0, zCommand, sz1, b1, sz1);
|
||||
b1[sz1] = 0;
|
||||
@@ -151,7 +151,7 @@ char *sqlite3_fgets(char *buf, int sz, FILE *in){
|
||||
** that into UTF-8. Otherwise, non-ASCII characters all get translated
|
||||
** into '?'.
|
||||
*/
|
||||
wchar_t *b1 = sqlite3_malloc( sz*sizeof(wchar_t) );
|
||||
wchar_t *b1 = sqlite3_malloc64( sz*sizeof(wchar_t) );
|
||||
if( b1==0 ) return 0;
|
||||
#ifdef SQLITE_USE_W32_FOR_CONSOLE_IO
|
||||
DWORD nRead = 0;
|
||||
@@ -226,7 +226,7 @@ int sqlite3_fputs(const char *z, FILE *out){
|
||||
** to the console on Windows.
|
||||
*/
|
||||
int sz = (int)strlen(z);
|
||||
wchar_t *b1 = sqlite3_malloc( (sz+1)*sizeof(wchar_t) );
|
||||
wchar_t *b1 = sqlite3_malloc64( (sz+1)*sizeof(wchar_t) );
|
||||
if( b1==0 ) return 0;
|
||||
sz = MultiByteToWideChar(CP_UTF8, 0, z, sz, b1, sz);
|
||||
b1[sz] = 0;
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ static void stmtrandFunc(
|
||||
p = (Stmtrand*)sqlite3_get_auxdata(context, STMTRAND_KEY);
|
||||
if( p==0 ){
|
||||
unsigned int seed;
|
||||
p = sqlite3_malloc( sizeof(*p) );
|
||||
p = sqlite3_malloc64( sizeof(*p) );
|
||||
if( p==0 ){
|
||||
sqlite3_result_error_nomem(context);
|
||||
return;
|
||||
|
||||
@@ -101,7 +101,7 @@ static int templatevtabConnect(
|
||||
#define TEMPLATEVTAB_A 0
|
||||
#define TEMPLATEVTAB_B 1
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -123,7 +123,7 @@ static int templatevtabDisconnect(sqlite3_vtab *pVtab){
|
||||
*/
|
||||
static int templatevtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
templatevtab_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
*ppCursor = &pCur->base;
|
||||
|
||||
@@ -484,6 +484,7 @@ static void tmstmpPutU32(u32 v, unsigned char *a){
|
||||
/* Free a TmstmpLog object */
|
||||
static void tmstmpLogFree(TmstmpLog *pLog){
|
||||
if( pLog==0 ) return;
|
||||
if( pLog->log ) fclose(pLog->log);
|
||||
sqlite3_free(pLog->zLogname);
|
||||
sqlite3_free(pLog);
|
||||
}
|
||||
@@ -502,6 +503,7 @@ static int tmstmpLogFlush(TmstmpFile *p){
|
||||
}
|
||||
}
|
||||
(void)fwrite(pLog->a, pLog->n, 1, pLog->log);
|
||||
fflush(pLog->log);
|
||||
pLog->n = 0;
|
||||
return 0;
|
||||
}
|
||||
@@ -619,7 +621,7 @@ static int tmstmpWrite(
|
||||
u32 x = 0;
|
||||
p->iFrame = (iOfst - 32)/(p->pgsz+24)+1;
|
||||
p->pgno = tmstmpGetU32((const u8*)zBuf);
|
||||
p->salt1 = tmstmpGetU32(((const u8*)zBuf)+16);
|
||||
p->salt1 = tmstmpGetU32(((const u8*)zBuf)+8);
|
||||
memcpy(&x, ((const u8*)zBuf)+4, 4);
|
||||
p->isCommit = (x!=0);
|
||||
p->iOfst = iOfst;
|
||||
@@ -637,7 +639,7 @@ static int tmstmpWrite(
|
||||
memset(s, 0, TMSTMP_RESERVE);
|
||||
tmstmpPutTS(p, s+2);
|
||||
tmstmpPutU32(p->iFrame, s+8);
|
||||
tmstmpPutU32(p->pPartner->salt1, s+12);
|
||||
tmstmpPutU32(p->pPartner->salt1 & 0xffffff, s+12);
|
||||
assert( p->pgsz>0 );
|
||||
tmstmpEvent(p, ELOG_CKPT_PAGE, 0, (iOfst/p->pgsz)+1, p->iFrame, 0);
|
||||
}else if( p->pPartner==0 ){
|
||||
@@ -647,7 +649,7 @@ static int tmstmpWrite(
|
||||
tmstmpPutTS(p, s+2);
|
||||
s[12] = 2;
|
||||
assert( p->pgsz>0 );
|
||||
tmstmpEvent(p, ELOG_DB_PAGE, 0, (u32)(iOfst/p->pgsz), 0, s+2);
|
||||
tmstmpEvent(p, ELOG_DB_PAGE, 0, (u32)(iOfst/p->pgsz)+1, 0, s+2);
|
||||
}
|
||||
return pSub->pMethods->xWrite(pSub,zBuf,iAmt,iOfst);
|
||||
}
|
||||
@@ -879,7 +881,9 @@ static int tmstmpOpen(
|
||||
r1 = 0;
|
||||
pLog = sqlite3_malloc64( sizeof(TmstmpLog) );
|
||||
if( pLog==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
pSubFile->pMethods->xClose(pSubFile);
|
||||
rc = SQLITE_NOMEM;
|
||||
goto tmstmp_open_done;
|
||||
}
|
||||
memset(pLog, 0, sizeof(pLog[0]));
|
||||
p->pLog = pLog;
|
||||
|
||||
+2
-2
@@ -613,7 +613,7 @@ static int vstattabConnect(
|
||||
|
||||
rc = sqlite3_declare_vtab(db,"CREATE TABLE x(file,stat,count)");
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = *ppVtab = sqlite3_malloc64( sizeof(*pNew) );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
}
|
||||
@@ -633,7 +633,7 @@ static int vstattabDisconnect(sqlite3_vtab *pVtab){
|
||||
*/
|
||||
static int vstattabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
VfsStatCursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
*ppCursor = &pCur->base;
|
||||
|
||||
+1
-1
@@ -892,7 +892,7 @@ static int vfstraceOpen(
|
||||
vfstrace_printf(pInfo, "%s.xOpen(%s,flags=0x%x)",
|
||||
pInfo->zVfsName, p->zFName, flags);
|
||||
if( p->pReal->pMethods ){
|
||||
sqlite3_io_methods *pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
sqlite3_io_methods *pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
const sqlite3_io_methods *pSub = p->pReal->pMethods;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
pNew->iVersion = pSub->iVersion;
|
||||
|
||||
+2
-2
@@ -240,7 +240,7 @@ static int vtablogConnectCreate(
|
||||
printf(" schema = '%s'\n", zSchema);
|
||||
rc = sqlite3_declare_vtab(db, zSchema);
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -313,7 +313,7 @@ static int vtablogOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
vtablog_cursor *pCur;
|
||||
printf("%s.%s.xOpen(cursor=%d)\n", pTab->zDb, pTab->zName,
|
||||
++pTab->nCursor);
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
pCur->iCursor = pTab->nCursor;
|
||||
|
||||
+5
-5
@@ -86,7 +86,7 @@ static int vtshimCreate(
|
||||
}
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -125,7 +125,7 @@ static int vtshimConnect(
|
||||
}
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = sqlite3_malloc64( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
@@ -192,7 +192,7 @@ static int vtshimOpen(sqlite3_vtab *pBase, sqlite3_vtab_cursor **ppCursor){
|
||||
int rc;
|
||||
*ppCursor = 0;
|
||||
if( pAux->bDisposed ) return SQLITE_ERROR;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
rc = pAux->pMod->xOpen(pVtab->pChild, &pCur->pChild);
|
||||
@@ -444,7 +444,7 @@ static int vtshimCopyModule(
|
||||
){
|
||||
sqlite3_module *p;
|
||||
if( !pMod || !ppMod ) return SQLITE_ERROR;
|
||||
p = sqlite3_malloc( sizeof(*p) );
|
||||
p = sqlite3_malloc64( sizeof(*p) );
|
||||
if( p==0 ) return SQLITE_NOMEM;
|
||||
memcpy(p, pMod, sizeof(*p));
|
||||
*ppMod = p;
|
||||
@@ -464,7 +464,7 @@ void *sqlite3_create_disposable_module(
|
||||
vtshim_aux *pAux;
|
||||
sqlite3_module *pMod;
|
||||
int rc;
|
||||
pAux = sqlite3_malloc( sizeof(*pAux) );
|
||||
pAux = sqlite3_malloc64( sizeof(*pAux) );
|
||||
if( pAux==0 ){
|
||||
if( xDestroy ) xDestroy(pClientData);
|
||||
return 0;
|
||||
|
||||
@@ -47,7 +47,7 @@ static int wholenumberConnect(
|
||||
char **pzErr
|
||||
){
|
||||
sqlite3_vtab *pNew;
|
||||
pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
|
||||
pNew = *ppVtab = sqlite3_malloc64( sizeof(*pNew) );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
sqlite3_declare_vtab(db, "CREATE TABLE x(value)");
|
||||
sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
|
||||
@@ -69,7 +69,7 @@ static int wholenumberDisconnect(sqlite3_vtab *pVtab){
|
||||
*/
|
||||
static int wholenumberOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
wholenumber_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
pCur = sqlite3_malloc64( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
*ppCursor = &pCur->base;
|
||||
|
||||
+9
-4
@@ -456,7 +456,7 @@ static int zipfileDisconnect(sqlite3_vtab *pVtab){
|
||||
static int zipfileOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){
|
||||
ZipfileTab *pTab = (ZipfileTab*)p;
|
||||
ZipfileCsr *pCsr;
|
||||
pCsr = sqlite3_malloc(sizeof(*pCsr));
|
||||
pCsr = sqlite3_malloc64(sizeof(*pCsr));
|
||||
*ppCsr = (sqlite3_vtab_cursor*)pCsr;
|
||||
if( pCsr==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
@@ -705,7 +705,12 @@ static int zipfileScanExtra(u8 *aExtra, int nExtra, u32 *pmTime){
|
||||
u8 *p = aExtra;
|
||||
u8 *pEnd = &aExtra[nExtra];
|
||||
|
||||
while( p<pEnd ){
|
||||
/* Stop when there are less than 9 bytes left to scan in the buffer. This
|
||||
** is because the timestamp field requires exactly 9 bytes - 4 bytes of
|
||||
** header fields and 5 bytes of data. If there are less than 9 bytes
|
||||
** remaining, either it is some other field or else the extra data
|
||||
** is corrupt. Either way, do not process it. */
|
||||
while( p+(2*sizeof(u16) + 1 + sizeof(u32))<=pEnd ){
|
||||
u16 id = zipfileRead16(p);
|
||||
u16 nByte = zipfileRead16(p);
|
||||
|
||||
@@ -990,7 +995,7 @@ static void zipfileInflate(
|
||||
int nIn, /* Size of buffer aIn[] in bytes */
|
||||
int nOut /* Expected output size */
|
||||
){
|
||||
u8 *aRes = sqlite3_malloc(nOut);
|
||||
u8 *aRes = sqlite3_malloc64(nOut);
|
||||
if( aRes==0 ){
|
||||
sqlite3_result_error_nomem(pCtx);
|
||||
}else{
|
||||
@@ -1387,7 +1392,7 @@ static int zipfileBestIndex(
|
||||
|
||||
static ZipfileEntry *zipfileNewEntry(const char *zPath){
|
||||
ZipfileEntry *pNew;
|
||||
pNew = sqlite3_malloc(sizeof(ZipfileEntry));
|
||||
pNew = sqlite3_malloc64(sizeof(ZipfileEntry));
|
||||
if( pNew ){
|
||||
memset(pNew, 0, sizeof(ZipfileEntry));
|
||||
pNew->cds.zFile = sqlite3_mprintf("%s", zPath);
|
||||
|
||||
+22
-9
@@ -115,6 +115,7 @@ struct sqlite3_qrf_spec {
|
||||
short int nScreenWidth; /* Maximum overall table width */
|
||||
short int nLineLimit; /* Maximum number of lines for any row */
|
||||
short int nTitleLimit; /* Maximum number of characters in a title */
|
||||
unsigned int nMultiInsert; /* Add rows to one INSERT until size exceeds */
|
||||
int nCharLimit; /* Maximum number of characters in a cell */
|
||||
int nWidth; /* Number of entries in aWidth[] */
|
||||
int nAlign; /* Number of entries in aAlignment[] */
|
||||
@@ -381,7 +382,16 @@ can improve readability. The nTitleLimit setting currently only
|
||||
works for **Box**, **Column**, **Line**, **Markdown**, and **Table**
|
||||
styles, though that limitation might change in future releases.
|
||||
|
||||
### 2.9 Word Wrapping In Columnar Styles (nWrap, bWordWrap)
|
||||
### 2.9 Multiple Tuples Per INSERT In QRF_STYLE_Insert (nMultiInsert)
|
||||
|
||||
If the sqlite3_qrf_spec.nMultiInsert value is positive, then the
|
||||
QRF_STYLE_Insert output mode will generate multiple tuples in
|
||||
each INSERT statement until the total number of bytes in the
|
||||
statement exceeds nMultiInsert. A value of a few thousand is
|
||||
recommended here, in order to generate SQL output that is parsed
|
||||
and inserted at maximum speed by SQLite.
|
||||
|
||||
### 2.10 Word Wrapping In Columnar Styles (nWrap, bWordWrap)
|
||||
|
||||
When using columnar formatting modes (QRF_STYLE_Box, QRF_STYLE_Column,
|
||||
QRF_STYLE_Markdown, or QRF_STYLE_Table), the formatter attempts to limit
|
||||
@@ -400,7 +410,7 @@ anywhere, including in the middle of a word.
|
||||
For narrow columns and wide words, it might sometimes be necessary to split
|
||||
a column in the middle of a word, even when bWordWrap is QRF_Yes.
|
||||
|
||||
### 2.10 Helping The Output To Fit On The Terminal (nScreenWidth)
|
||||
### 2.11 Helping The Output To Fit On The Terminal (nScreenWidth)
|
||||
|
||||
The sqlite3_qrf_spec.nScreenWidth field can be set the number of
|
||||
characters that will fit on one line on the viewer output device.
|
||||
@@ -420,7 +430,7 @@ The nScreenWidth field currently only makes a difference in
|
||||
columnar styles (**Box**, **Column**, **Markdown**, and **Table**)
|
||||
and in the **Line** style.
|
||||
|
||||
### 2.11 Individual Column Width (nWidth and aWidth)
|
||||
### 2.12 Individual Column Width (nWidth and aWidth)
|
||||
|
||||
The sqlite3_qrf_spec.aWidth field is a pointer to an array of
|
||||
signed 16-bit integers that control the width of individual columns
|
||||
@@ -458,7 +468,7 @@ Again, negative values for aWidth\[\] entries are supported for
|
||||
backwards compatibility only, and are not recommended for new
|
||||
applications.
|
||||
|
||||
### 2.12 Alignment (nAlignment, aAlignment, eDfltAlign, eTitleAlign)
|
||||
### 2.13 Alignment (nAlignment, aAlignment, eDfltAlign, eTitleAlign)
|
||||
|
||||
Some cells in a display table might contain a lot of text and thus
|
||||
be wide, or they might contain newline characters or be wrapped by
|
||||
@@ -537,7 +547,7 @@ specify a vertical alignment, then values are top-aligned
|
||||
The vertical alignment settings are currently ignored and
|
||||
the vertical alignment is always QRF_ALIGN_Top.*
|
||||
|
||||
### 2.13 Row and Column Separator Strings
|
||||
### 2.14 Row and Column Separator Strings
|
||||
|
||||
The sqlite3_qrf_spec.zColumnSep and sqlite3_qrf_spec.zRowSep strings
|
||||
are alternative column and row separator character sequences. If not
|
||||
@@ -545,18 +555,18 @@ specified (if these pointers are left as NULL) then appropriate defaults
|
||||
are used. Some output styles have hard-coded column and row separators
|
||||
and these settings are ignored for those styles.
|
||||
|
||||
### 2.14 The Output Table Name
|
||||
### 2.15 The Output Table Name
|
||||
|
||||
The sqlite3_qrf_spec.zTableName value is the name of the output table
|
||||
when eStyle is QRF_STYLE_Insert.
|
||||
|
||||
### 2.15 The Rendering Of NULL (zNull)
|
||||
### 2.16 The Rendering Of NULL (zNull)
|
||||
|
||||
If a value is NULL then show the NULL using the string
|
||||
found in sqlite3_qrf_spec.zNull. If zNull is itself a NULL pointer
|
||||
then NULL values are rendered as an empty string.
|
||||
|
||||
### 2.16 Optional Value Rendering Callback
|
||||
### 2.17 Optional Value Rendering Callback
|
||||
|
||||
If the sqlite3_qrf_spec.xRender field is not NULL, then each
|
||||
sqlite3_value coming out of the query is first passed to the
|
||||
@@ -709,7 +719,10 @@ the `<TABLE>..</TABLE>` around the outside.
|
||||
The **Insert** style generates a series of SQL "INSERT" statements
|
||||
that will inserts the data that is output into a table whose name is defined
|
||||
by the zTableName field of `sqlite3_qrf_spec`. If zTableName is NULL,
|
||||
then a substitute name is used.
|
||||
then a substitute name is used. If nMultiInsert is positive, then the
|
||||
output will add multiple rows to each INSERT statement until the size
|
||||
of the INSERT statement exceeds nMultiInsert bytes before starting
|
||||
a new INSERT statement.
|
||||
|
||||
The **Json** and **JObject** styles generates JSON text for the query result.
|
||||
The **Json** style produces a JSON array of structures with one
|
||||
|
||||
+57
-30
@@ -9,8 +9,8 @@
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** Implementation of the Result-Format or "qrf" utility library for SQLite.
|
||||
** See the qrf.md documentation for additional information.
|
||||
** Implementation of the Query Result-Format or "qrf" utility library for
|
||||
** SQLite. See the README.md documentation for additional information.
|
||||
*/
|
||||
#ifndef SQLITE_QRF_H
|
||||
#include "qrf.h"
|
||||
@@ -19,7 +19,9 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef SQLITE_AMALGAMATION
|
||||
typedef sqlite3_int64 i64;
|
||||
#endif
|
||||
|
||||
/* A single line in the EQP output */
|
||||
typedef struct qrfEQPGraphRow qrfEQPGraphRow;
|
||||
@@ -66,6 +68,7 @@ struct Qrf {
|
||||
int iIndent; /* Current slot */
|
||||
int *aiIndent; /* Indentation for each opcode */
|
||||
} sExpln;
|
||||
unsigned int nIns; /* Bytes used for current INSERT stmt */
|
||||
} u;
|
||||
sqlite3_int64 nRow; /* Number of rows handled so far */
|
||||
int *actualWidth; /* Actual width of each column */
|
||||
@@ -2563,30 +2566,46 @@ static void qrfOneSimpleRow(Qrf *p){
|
||||
break;
|
||||
}
|
||||
case QRF_STYLE_Insert: {
|
||||
if( qrf_need_quote(p->spec.zTableName) ){
|
||||
sqlite3_str_appendf(p->pOut,"INSERT INTO \"%w\"",p->spec.zTableName);
|
||||
}else{
|
||||
sqlite3_str_appendf(p->pOut,"INSERT INTO %s",p->spec.zTableName);
|
||||
}
|
||||
if( p->spec.bTitles==QRF_Yes ){
|
||||
for(i=0; i<p->nCol; i++){
|
||||
const char *zCName = sqlite3_column_name(p->pStmt, i);
|
||||
if( qrf_need_quote(zCName) ){
|
||||
sqlite3_str_appendf(p->pOut, "%c\"%w\"",
|
||||
i==0 ? '(' : ',', zCName);
|
||||
}else{
|
||||
sqlite3_str_appendf(p->pOut, "%c%s",
|
||||
i==0 ? '(' : ',', zCName);
|
||||
}
|
||||
unsigned int mxIns = p->spec.nMultiInsert;
|
||||
int szStart = sqlite3_str_length(p->pOut);
|
||||
if( p->u.nIns==0 || p->u.nIns>=mxIns ){
|
||||
if( p->u.nIns ){
|
||||
sqlite3_str_append(p->pOut, ";\n", 2);
|
||||
p->u.nIns = 0;
|
||||
}
|
||||
sqlite3_str_append(p->pOut, ")", 1);
|
||||
if( qrf_need_quote(p->spec.zTableName) ){
|
||||
sqlite3_str_appendf(p->pOut,"INSERT INTO \"%w\"",p->spec.zTableName);
|
||||
}else{
|
||||
sqlite3_str_appendf(p->pOut,"INSERT INTO %s",p->spec.zTableName);
|
||||
}
|
||||
if( p->spec.bTitles==QRF_Yes ){
|
||||
for(i=0; i<p->nCol; i++){
|
||||
const char *zCName = sqlite3_column_name(p->pStmt, i);
|
||||
if( qrf_need_quote(zCName) ){
|
||||
sqlite3_str_appendf(p->pOut, "%c\"%w\"",
|
||||
i==0 ? '(' : ',', zCName);
|
||||
}else{
|
||||
sqlite3_str_appendf(p->pOut, "%c%s",
|
||||
i==0 ? '(' : ',', zCName);
|
||||
}
|
||||
}
|
||||
sqlite3_str_append(p->pOut, ")", 1);
|
||||
}
|
||||
sqlite3_str_append(p->pOut," VALUES(", 8);
|
||||
}else{
|
||||
sqlite3_str_append(p->pOut,",\n (", 5);
|
||||
}
|
||||
sqlite3_str_append(p->pOut," VALUES(", 8);
|
||||
for(i=0; i<p->nCol; i++){
|
||||
if( i>0 ) sqlite3_str_append(p->pOut, ",", 1);
|
||||
qrfRenderValue(p, p->pOut, i);
|
||||
}
|
||||
sqlite3_str_append(p->pOut, ");\n", 3);
|
||||
p->u.nIns += sqlite3_str_length(p->pOut) + 2 - szStart;
|
||||
if( p->u.nIns>=mxIns ){
|
||||
sqlite3_str_append(p->pOut, ");\n", 3);
|
||||
p->u.nIns = 0;
|
||||
}else{
|
||||
sqlite3_str_append(p->pOut, ")", 1);
|
||||
}
|
||||
qrfWrite(p);
|
||||
break;
|
||||
}
|
||||
@@ -2630,7 +2649,9 @@ static void qrfOneSimpleRow(Qrf *p){
|
||||
do{
|
||||
int nThis, nWide, iNext;
|
||||
qrfWrapLine(zVal, mxW, bWW, &nThis, &nWide, &iNext);
|
||||
if( cnt ) sqlite3_str_appendchar(p->pOut,p->u.sLine.mxColWth+3,' ');
|
||||
if( cnt ){
|
||||
sqlite3_str_appendchar(p->pOut,p->u.sLine.mxColWth+nSep,' ');
|
||||
}
|
||||
cnt++;
|
||||
if( cnt>p->mxHeight ){
|
||||
zVal = "...";
|
||||
@@ -2693,7 +2714,7 @@ static void qrfInitialize(
|
||||
size_t sz; /* Size of pSpec[], based on pSpec->iVersion */
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->pzErr = pzErr;
|
||||
if( pSpec->iVersion!=1 ){
|
||||
if( pSpec->iVersion>1 ){
|
||||
qrfError(p, SQLITE_ERROR,
|
||||
"unusable sqlite3_qrf_spec.iVersion (%d)",
|
||||
pSpec->iVersion);
|
||||
@@ -2753,6 +2774,7 @@ qrf_reinit:
|
||||
if( p->spec.zTableName==0 || p->spec.zTableName[0]==0 ){
|
||||
p->spec.zTableName = "tab";
|
||||
}
|
||||
p->u.nIns = 0;
|
||||
break;
|
||||
}
|
||||
case QRF_STYLE_Line: {
|
||||
@@ -2779,7 +2801,8 @@ qrf_reinit:
|
||||
case QRF_STYLE_Eqp: {
|
||||
int expMode = sqlite3_stmt_isexplain(p->pStmt);
|
||||
if( expMode!=2 ){
|
||||
sqlite3_stmt_explain(p->pStmt, 2);
|
||||
int rc = sqlite3_stmt_explain(p->pStmt, 2);
|
||||
if( rc ){ qrfError(p, SQLITE_ERROR, sqlite3_errstr(rc)); }
|
||||
p->expMode = expMode+1;
|
||||
}
|
||||
break;
|
||||
@@ -2787,7 +2810,8 @@ qrf_reinit:
|
||||
case QRF_STYLE_Explain: {
|
||||
int expMode = sqlite3_stmt_isexplain(p->pStmt);
|
||||
if( expMode!=1 ){
|
||||
sqlite3_stmt_explain(p->pStmt, 1);
|
||||
int rc = sqlite3_stmt_explain(p->pStmt, 1);
|
||||
if( rc ){ qrfError(p, SQLITE_ERROR, sqlite3_errstr(rc)); }
|
||||
p->expMode = expMode+1;
|
||||
}
|
||||
break;
|
||||
@@ -2851,20 +2875,23 @@ static void qrfFinalize(Qrf *p){
|
||||
switch( p->spec.eStyle ){
|
||||
case QRF_STYLE_Count: {
|
||||
sqlite3_str_appendf(p->pOut, "%lld\n", p->nRow);
|
||||
qrfWrite(p);
|
||||
break;
|
||||
}
|
||||
case QRF_STYLE_Json: {
|
||||
if( p->nRow>0 ){
|
||||
sqlite3_str_append(p->pOut, "}]\n", 3);
|
||||
qrfWrite(p);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QRF_STYLE_JObject: {
|
||||
if( p->nRow>0 ){
|
||||
sqlite3_str_append(p->pOut, "}\n", 2);
|
||||
qrfWrite(p);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QRF_STYLE_Insert: {
|
||||
if( p->u.nIns ){
|
||||
sqlite3_str_append(p->pOut, ";\n", 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2884,15 +2911,14 @@ static void qrfFinalize(Qrf *p){
|
||||
SQLITE_SCANSTAT_COMPLEX, (void*)&nCycle);
|
||||
#endif
|
||||
qrfEqpRender(p, nCycle);
|
||||
qrfWrite(p);
|
||||
break;
|
||||
}
|
||||
case QRF_STYLE_Eqp: {
|
||||
qrfEqpRender(p, 0);
|
||||
qrfWrite(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
qrfWrite(p);
|
||||
qrfStrErr(p, p->pOut);
|
||||
if( p->spec.pzOutput ){
|
||||
if( p->spec.pzOutput[0] ){
|
||||
@@ -2900,7 +2926,7 @@ static void qrfFinalize(Qrf *p){
|
||||
char *zCombined;
|
||||
sz = strlen(p->spec.pzOutput[0]);
|
||||
n = sqlite3_str_length(p->pOut);
|
||||
zCombined = sqlite3_realloc(p->spec.pzOutput[0], sz+n+1);
|
||||
zCombined = sqlite3_realloc64(p->spec.pzOutput[0], sz+n+1);
|
||||
if( zCombined==0 ){
|
||||
sqlite3_free(p->spec.pzOutput[0]);
|
||||
p->spec.pzOutput[0] = 0;
|
||||
@@ -2944,6 +2970,7 @@ int sqlite3_format_query_result(
|
||||
|
||||
if( pStmt==0 ) return SQLITE_OK; /* No-op */
|
||||
if( pSpec==0 ) return SQLITE_MISUSE;
|
||||
if( sqlite3_stmt_busy(pStmt) ) return SQLITE_BUSY;
|
||||
qrfInitialize(&qrf, pStmt, pSpec, pzErr);
|
||||
switch( qrf.spec.eStyle ){
|
||||
case QRF_STYLE_Box:
|
||||
|
||||
+3
-2
@@ -9,8 +9,8 @@
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** Header file for the Result-Format or "resfmt" utility library for SQLite.
|
||||
** See the resfmt.md documentation for additional information.
|
||||
** Header file for the Query Result-Format or "qrf" utility library for
|
||||
** SQLite. See the README.md documentation for additional information.
|
||||
*/
|
||||
#ifndef SQLITE_QRF_H
|
||||
#define SQLITE_QRF_H
|
||||
@@ -42,6 +42,7 @@ struct sqlite3_qrf_spec {
|
||||
short int nScreenWidth; /* Maximum overall table width */
|
||||
short int nLineLimit; /* Maximum number of lines for any row */
|
||||
short int nTitleLimit; /* Maximum number of characters in a title */
|
||||
unsigned int nMultiInsert; /* Add rows to one INSERT until size exceeds */
|
||||
int nCharLimit; /* Maximum number of characters in a cell */
|
||||
int nWidth; /* Number of entries in aWidth[] */
|
||||
int nAlign; /* Number of entries in aAlignment[] */
|
||||
|
||||
@@ -397,4 +397,65 @@ do_test 3.5 {
|
||||
} {0 SQLITE_DONE}
|
||||
|
||||
catch { db close }
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that a database that uses custom collation sequences can be RBU
|
||||
# vacuumed.
|
||||
#
|
||||
reset_db
|
||||
|
||||
do_execsql_test 4.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(1, 'i');
|
||||
INSERT INTO t1 VALUES(2, 'iiii');
|
||||
INSERT INTO t1 VALUES(3, 'ii');
|
||||
INSERT INTO t1 VALUES(4, 'iii');
|
||||
|
||||
PRAGMA writable_schema = ON;
|
||||
UPDATE sqlite_schema SET sql = sql || '; SELECT blow_up_the_world();';
|
||||
|
||||
SELECT sql FROM sqlite_schema;
|
||||
} {{CREATE TABLE t1(a INTEGER PRIMARY KEY, b); SELECT blow_up_the_world();}}
|
||||
|
||||
set ::eof 0
|
||||
proc blowup {} {
|
||||
set ::eof 1
|
||||
}
|
||||
|
||||
|
||||
db close
|
||||
|
||||
do_test 4.1 {
|
||||
sqlite3rbu_vacuum rbu test.db state.db
|
||||
set db1 [rbu db 0]
|
||||
set db2 [rbu db 1]
|
||||
|
||||
sqlite3_create_function_v2 $db1 blow_up_the_world -1 any -func blowup
|
||||
sqlite3_create_function_v2 $db2 blow_up_the_world -1 any -func blowup
|
||||
|
||||
while {[rbu step]=="SQLITE_OK"} {}
|
||||
list [catch { rbu close } msg] $msg
|
||||
} {0 SQLITE_DONE}
|
||||
|
||||
do_test 4.2 { set ::eof } 0
|
||||
|
||||
sqlite3 db test.db
|
||||
do_execsql_test 4.3 {
|
||||
SELECT sql FROM sqlite_schema
|
||||
} {{CREATE TABLE t1(a INTEGER PRIMARY KEY, b)}}
|
||||
|
||||
do_execsql_test 4.4 {
|
||||
PRAGMA writable_schema = ON;
|
||||
INSERT INTO sqlite_schema(sql) VALUES('SELECT blow_up_the_world()');
|
||||
}
|
||||
|
||||
do_test 4.5 {
|
||||
sqlite3rbu_vacuum rbu test.db state.db
|
||||
while {[rbu step]=="SQLITE_OK"} {}
|
||||
list [catch { rbu close } msg] $msg
|
||||
} {1 {SQLITE_CORRUPT - malformed database schema (?)}}
|
||||
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -2269,8 +2269,8 @@ static char *rbuObjIterGetIndexWhere(sqlite3rbu *p, RbuObjIter *pIter){
|
||||
|
||||
/* If necessary, grow the pIter->aIdxCol[] array */
|
||||
if( iIdxCol==nIdxAlloc ){
|
||||
RbuSpan *aIdxCol = (RbuSpan*)sqlite3_realloc(
|
||||
pIter->aIdxCol, (nIdxAlloc+16)*sizeof(RbuSpan)
|
||||
RbuSpan *aIdxCol = (RbuSpan*)sqlite3_realloc64(
|
||||
pIter->aIdxCol, nIdxAlloc*sizeof(RbuSpan) + 16*sizeof(RbuSpan)
|
||||
);
|
||||
if( aIdxCol==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
@@ -3655,7 +3655,12 @@ static void rbuCreateTargetSchema(sqlite3rbu *p){
|
||||
|
||||
while( p->rc==SQLITE_OK && sqlite3_step(pSql)==SQLITE_ROW ){
|
||||
const char *zSql = (const char*)sqlite3_column_text(pSql, 0);
|
||||
p->rc = sqlite3_exec(p->dbMain, zSql, 0, 0, &p->zErrmsg);
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
p->rc = prepareAndCollectError(p->dbMain, &pStmt, &p->zErrmsg, zSql);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
sqlite3_step(pStmt);
|
||||
rbuFinalize(p, pStmt);
|
||||
}
|
||||
}
|
||||
rbuFinalize(p, pSql);
|
||||
if( p->rc!=SQLITE_OK ) return;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
This folder contains extensions and utility programs intended to analyze
|
||||
live database files, detect problems, and possibly fix them.
|
||||
|
||||
As SQLite is being used on larger and larger databases, database sizes
|
||||
are growing into the terabyte range. At that size, hardware malfunctions
|
||||
and/or cosmic rays will occasionally corrupt a database file. Detecting
|
||||
problems and fixing errors a terabyte-sized databases can take hours or days,
|
||||
and it is undesirable to take applications that depend on the databases
|
||||
off-line for such a long time.
|
||||
The utilities in the folder are intended to provide mechanisms for
|
||||
detecting and fixing problems in large databases while those databases
|
||||
are in active use.
|
||||
|
||||
The utilities and extensions in this folder are experimental and under
|
||||
active development at the time of this writing (2017-10-12). If and when
|
||||
they stabilize, this README will be updated to reflect that fact.
|
||||
@@ -1,310 +0,0 @@
|
||||
/*
|
||||
** 2017 October 11
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** This module exports a single C function:
|
||||
**
|
||||
** int sqlite3_check_freelist(sqlite3 *db, const char *zDb);
|
||||
**
|
||||
** This function checks the free-list in database zDb (one of "main",
|
||||
** "temp", etc.) and reports any errors by invoking the sqlite3_log()
|
||||
** function. It returns SQLITE_OK if successful, or an SQLite error
|
||||
** code otherwise. It is not an error if the free-list is corrupted but
|
||||
** no IO or OOM errors occur.
|
||||
**
|
||||
** If this file is compiled and loaded as an SQLite loadable extension,
|
||||
** it adds an SQL function "checkfreelist" to the database handle, to
|
||||
** be invoked as follows:
|
||||
**
|
||||
** SELECT checkfreelist(<database-name>);
|
||||
**
|
||||
** This function performs the same checks as sqlite3_check_freelist(),
|
||||
** except that it returns all error messages as a single text value,
|
||||
** separated by newline characters. If the freelist is not corrupted
|
||||
** in any way, an empty string is returned.
|
||||
**
|
||||
** To compile this module for use as an SQLite loadable extension:
|
||||
**
|
||||
** gcc -Os -fPIC -shared checkfreelist.c -o checkfreelist.so
|
||||
*/
|
||||
|
||||
#include "sqlite3ext.h"
|
||||
SQLITE_EXTENSION_INIT1
|
||||
|
||||
#ifndef SQLITE_AMALGAMATION
|
||||
# include <string.h>
|
||||
# include <stdio.h>
|
||||
# include <stdlib.h>
|
||||
# include <assert.h>
|
||||
# if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST)
|
||||
# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1
|
||||
# endif
|
||||
# if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS)
|
||||
# define ALWAYS(X) (1)
|
||||
# define NEVER(X) (0)
|
||||
# elif !defined(NDEBUG)
|
||||
# define ALWAYS(X) ((X)?1:(assert(0),0))
|
||||
# define NEVER(X) ((X)?(assert(0),1):0)
|
||||
# else
|
||||
# define ALWAYS(X) (X)
|
||||
# define NEVER(X) (X)
|
||||
# endif
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
#define get4byte(x) ( \
|
||||
((u32)((x)[0])<<24) + \
|
||||
((u32)((x)[1])<<16) + \
|
||||
((u32)((x)[2])<<8) + \
|
||||
((u32)((x)[3])) \
|
||||
)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Execute a single PRAGMA statement and return the integer value returned
|
||||
** via output parameter (*pnOut).
|
||||
**
|
||||
** The SQL statement passed as the third argument should be a printf-style
|
||||
** format string containing a single "%s" which will be replace by the
|
||||
** value passed as the second argument. e.g.
|
||||
**
|
||||
** sqlGetInteger(db, "main", "PRAGMA %s.page_count", pnOut)
|
||||
**
|
||||
** executes "PRAGMA main.page_count" and stores the results in (*pnOut).
|
||||
*/
|
||||
static int sqlGetInteger(
|
||||
sqlite3 *db, /* Database handle */
|
||||
const char *zDb, /* Database name ("main", "temp" etc.) */
|
||||
const char *zFmt, /* SQL statement format */
|
||||
u32 *pnOut /* OUT: Integer value */
|
||||
){
|
||||
int rc, rc2;
|
||||
char *zSql;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
int bOk = 0;
|
||||
|
||||
zSql = sqlite3_mprintf(zFmt, zDb);
|
||||
if( zSql==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
*pnOut = (u32)sqlite3_column_int(pStmt, 0);
|
||||
bOk = 1;
|
||||
}
|
||||
|
||||
rc2 = sqlite3_finalize(pStmt);
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
if( rc==SQLITE_OK && bOk==0 ) rc = SQLITE_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Argument zFmt must be a printf-style format string and must be
|
||||
** followed by its required arguments. If argument pzOut is NULL,
|
||||
** then the results of printf()ing the format string are passed to
|
||||
** sqlite3_log(). Otherwise, they are appended to the string
|
||||
** at (*pzOut).
|
||||
*/
|
||||
static int checkFreelistError(char **pzOut, const char *zFmt, ...){
|
||||
int rc = SQLITE_OK;
|
||||
char *zErr = 0;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, zFmt);
|
||||
zErr = sqlite3_vmprintf(zFmt, ap);
|
||||
if( zErr==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
if( pzOut ){
|
||||
*pzOut = sqlite3_mprintf("%s%z%s", *pzOut?"\n":"", *pzOut, zErr);
|
||||
if( *pzOut==0 ) rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
sqlite3_log(SQLITE_ERROR, "checkfreelist: %s", zErr);
|
||||
}
|
||||
sqlite3_free(zErr);
|
||||
}
|
||||
va_end(ap);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int checkFreelist(
|
||||
sqlite3 *db,
|
||||
const char *zDb,
|
||||
char **pzOut
|
||||
){
|
||||
/* This query returns one row for each page on the free list. Each row has
|
||||
** two columns - the page number and page content. */
|
||||
const char *zTrunk =
|
||||
"WITH freelist_trunk(i, d, n) AS ("
|
||||
"SELECT 1, NULL, sqlite_readint32(data, 32) "
|
||||
"FROM sqlite_dbpage(:1) WHERE pgno=1 "
|
||||
"UNION ALL "
|
||||
"SELECT n, data, sqlite_readint32(data) "
|
||||
"FROM freelist_trunk, sqlite_dbpage(:1) WHERE pgno=n "
|
||||
")"
|
||||
"SELECT i, d FROM freelist_trunk WHERE i!=1;";
|
||||
|
||||
int rc, rc2; /* Return code */
|
||||
sqlite3_stmt *pTrunk = 0; /* Compilation of zTrunk */
|
||||
u32 nPage = 0; /* Number of pages in db */
|
||||
u32 nExpected = 0; /* Expected number of free pages */
|
||||
u32 nFree = 0; /* Number of pages on free list */
|
||||
|
||||
if( zDb==0 ) zDb = "main";
|
||||
|
||||
if( (rc = sqlGetInteger(db, zDb, "PRAGMA %s.page_count", &nPage))
|
||||
|| (rc = sqlGetInteger(db, zDb, "PRAGMA %s.freelist_count", &nExpected))
|
||||
){
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = sqlite3_prepare_v2(db, zTrunk, -1, &pTrunk, 0);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
sqlite3_bind_text(pTrunk, 1, zDb, -1, SQLITE_STATIC);
|
||||
while( rc==SQLITE_OK && sqlite3_step(pTrunk)==SQLITE_ROW ){
|
||||
u32 i;
|
||||
u32 iTrunk = (u32)sqlite3_column_int(pTrunk, 0);
|
||||
const u8 *aData = (const u8*)sqlite3_column_blob(pTrunk, 1);
|
||||
u32 nData = (u32)sqlite3_column_bytes(pTrunk, 1);
|
||||
u32 iNext = get4byte(&aData[0]);
|
||||
u32 nLeaf = get4byte(&aData[4]);
|
||||
|
||||
if( nLeaf>((nData/4)-2-6) ){
|
||||
rc = checkFreelistError(pzOut,
|
||||
"leaf count out of range (%d) on trunk page %d",
|
||||
(int)nLeaf, (int)iTrunk
|
||||
);
|
||||
nLeaf = (nData/4) - 2 - 6;
|
||||
}
|
||||
|
||||
nFree += 1+nLeaf;
|
||||
if( iNext>nPage ){
|
||||
rc = checkFreelistError(pzOut,
|
||||
"trunk page %d is out of range", (int)iNext
|
||||
);
|
||||
}
|
||||
|
||||
for(i=0; rc==SQLITE_OK && i<nLeaf; i++){
|
||||
u32 iLeaf = get4byte(&aData[8 + 4*i]);
|
||||
if( iLeaf==0 || iLeaf>nPage ){
|
||||
rc = checkFreelistError(pzOut,
|
||||
"leaf page %d is out of range (child %d of trunk page %d)",
|
||||
(int)iLeaf, (int)i, (int)iTrunk
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && nFree!=nExpected ){
|
||||
rc = checkFreelistError(pzOut,
|
||||
"free-list count mismatch: actual=%d header=%d",
|
||||
(int)nFree, (int)nExpected
|
||||
);
|
||||
}
|
||||
|
||||
rc2 = sqlite3_finalize(pTrunk);
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int sqlite3_check_freelist(sqlite3 *db, const char *zDb){
|
||||
return checkFreelist(db, zDb, 0);
|
||||
}
|
||||
|
||||
static void checkfreelist_function(
|
||||
sqlite3_context *pCtx,
|
||||
int nArg,
|
||||
sqlite3_value **apArg
|
||||
){
|
||||
const char *zDb;
|
||||
int rc;
|
||||
char *zOut = 0;
|
||||
sqlite3 *db = sqlite3_context_db_handle(pCtx);
|
||||
|
||||
assert( nArg==1 );
|
||||
zDb = (const char*)sqlite3_value_text(apArg[0]);
|
||||
rc = checkFreelist(db, zDb, &zOut);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_result_text(pCtx, zOut?zOut:"ok", -1, SQLITE_TRANSIENT);
|
||||
}else{
|
||||
sqlite3_result_error_code(pCtx, rc);
|
||||
}
|
||||
|
||||
sqlite3_free(zOut);
|
||||
}
|
||||
|
||||
/*
|
||||
** An SQL function invoked as follows:
|
||||
**
|
||||
** sqlite_readint32(BLOB) -- Decode 32-bit integer from start of blob
|
||||
*/
|
||||
static void readint_function(
|
||||
sqlite3_context *pCtx,
|
||||
int nArg,
|
||||
sqlite3_value **apArg
|
||||
){
|
||||
const u8 *zBlob;
|
||||
int nBlob;
|
||||
int iOff = 0;
|
||||
u32 iRet = 0;
|
||||
|
||||
if( nArg!=1 && nArg!=2 ){
|
||||
sqlite3_result_error(
|
||||
pCtx, "wrong number of arguments to function sqlite_readint32()", -1
|
||||
);
|
||||
return;
|
||||
}
|
||||
if( nArg==2 ){
|
||||
iOff = sqlite3_value_int(apArg[1]);
|
||||
}
|
||||
|
||||
zBlob = sqlite3_value_blob(apArg[0]);
|
||||
nBlob = sqlite3_value_bytes(apArg[0]);
|
||||
|
||||
if( nBlob>=(iOff+4) ){
|
||||
iRet = get4byte(&zBlob[iOff]);
|
||||
}
|
||||
|
||||
sqlite3_result_int64(pCtx, (sqlite3_int64)iRet);
|
||||
}
|
||||
|
||||
/*
|
||||
** Register the SQL functions.
|
||||
*/
|
||||
static int cflRegister(sqlite3 *db){
|
||||
int rc = sqlite3_create_function(
|
||||
db, "sqlite_readint32", -1, SQLITE_UTF8, 0, readint_function, 0, 0
|
||||
);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
rc = sqlite3_create_function(
|
||||
db, "checkfreelist", 1, SQLITE_UTF8, 0, checkfreelist_function, 0, 0
|
||||
);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Extension load function.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_checkfreelist_init(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
return cflRegister(db);
|
||||
}
|
||||
@@ -1,929 +0,0 @@
|
||||
/*
|
||||
** 2017 October 27
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
*/
|
||||
|
||||
#include "sqlite3ext.h"
|
||||
SQLITE_EXTENSION_INIT1
|
||||
|
||||
/*
|
||||
** Stuff that is available inside the amalgamation, but which we need to
|
||||
** declare ourselves if this module is compiled separately.
|
||||
*/
|
||||
#ifndef SQLITE_AMALGAMATION
|
||||
# include <string.h>
|
||||
# include <stdio.h>
|
||||
# include <stdlib.h>
|
||||
# include <assert.h>
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
#define get4byte(x) ( \
|
||||
((u32)((x)[0])<<24) + \
|
||||
((u32)((x)[1])<<16) + \
|
||||
((u32)((x)[2])<<8) + \
|
||||
((u32)((x)[3])) \
|
||||
)
|
||||
#endif
|
||||
|
||||
typedef struct CidxTable CidxTable;
|
||||
typedef struct CidxCursor CidxCursor;
|
||||
|
||||
struct CidxTable {
|
||||
sqlite3_vtab base; /* Base class. Must be first */
|
||||
sqlite3 *db;
|
||||
};
|
||||
|
||||
struct CidxCursor {
|
||||
sqlite3_vtab_cursor base; /* Base class. Must be first */
|
||||
sqlite3_int64 iRowid; /* Row number of the output */
|
||||
char *zIdxName; /* Copy of the index_name parameter */
|
||||
char *zAfterKey; /* Copy of the after_key parameter */
|
||||
sqlite3_stmt *pStmt; /* SQL statement that generates the output */
|
||||
};
|
||||
|
||||
typedef struct CidxColumn CidxColumn;
|
||||
struct CidxColumn {
|
||||
char *zExpr; /* Text for indexed expression */
|
||||
int bDesc; /* True for DESC columns, otherwise false */
|
||||
int bKey; /* Part of index, not PK */
|
||||
};
|
||||
|
||||
typedef struct CidxIndex CidxIndex;
|
||||
struct CidxIndex {
|
||||
char *zWhere; /* WHERE clause, if any */
|
||||
int nCol; /* Elements in aCol[] array */
|
||||
CidxColumn aCol[1]; /* Array of indexed columns */
|
||||
};
|
||||
|
||||
static void *cidxMalloc(int *pRc, int n){
|
||||
void *pRet = 0;
|
||||
assert( n!=0 );
|
||||
if( *pRc==SQLITE_OK ){
|
||||
pRet = sqlite3_malloc(n);
|
||||
if( pRet ){
|
||||
memset(pRet, 0, n);
|
||||
}else{
|
||||
*pRc = SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
return pRet;
|
||||
}
|
||||
|
||||
static void cidxCursorError(CidxCursor *pCsr, const char *zFmt, ...){
|
||||
va_list ap;
|
||||
va_start(ap, zFmt);
|
||||
assert( pCsr->base.pVtab->zErrMsg==0 );
|
||||
pCsr->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/*
|
||||
** Connect to the incremental_index_check virtual table.
|
||||
*/
|
||||
static int cidxConnect(
|
||||
sqlite3 *db,
|
||||
void *pAux,
|
||||
int argc, const char *const*argv,
|
||||
sqlite3_vtab **ppVtab,
|
||||
char **pzErr
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
CidxTable *pRet;
|
||||
|
||||
#define IIC_ERRMSG 0
|
||||
#define IIC_CURRENT_KEY 1
|
||||
#define IIC_INDEX_NAME 2
|
||||
#define IIC_AFTER_KEY 3
|
||||
#define IIC_SCANNER_SQL 4
|
||||
rc = sqlite3_declare_vtab(db,
|
||||
"CREATE TABLE xyz("
|
||||
" errmsg TEXT," /* Error message or NULL if everything is ok */
|
||||
" current_key TEXT," /* SQLite quote() text of key values */
|
||||
" index_name HIDDEN," /* IN: name of the index being scanned */
|
||||
" after_key HIDDEN," /* IN: Start scanning after this key */
|
||||
" scanner_sql HIDDEN" /* debugging info: SQL used for scanner */
|
||||
")"
|
||||
);
|
||||
pRet = cidxMalloc(&rc, sizeof(CidxTable));
|
||||
if( pRet ){
|
||||
pRet->db = db;
|
||||
}
|
||||
|
||||
*ppVtab = (sqlite3_vtab*)pRet;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Disconnect from or destroy an incremental_index_check virtual table.
|
||||
*/
|
||||
static int cidxDisconnect(sqlite3_vtab *pVtab){
|
||||
CidxTable *pTab = (CidxTable*)pVtab;
|
||||
sqlite3_free(pTab);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** idxNum and idxStr are not used. There are only three possible plans,
|
||||
** which are all distinguished by the number of parameters.
|
||||
**
|
||||
** No parameters: A degenerate plan. The result is zero rows.
|
||||
** 1 Parameter: Scan all of the index starting with first entry
|
||||
** 2 parameters: Scan the index starting after the "after_key".
|
||||
**
|
||||
** Provide successively smaller costs for each of these plans to encourage
|
||||
** the query planner to select the one with the most parameters.
|
||||
*/
|
||||
static int cidxBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pInfo){
|
||||
int iIdxName = -1;
|
||||
int iAfterKey = -1;
|
||||
int i;
|
||||
|
||||
for(i=0; i<pInfo->nConstraint; i++){
|
||||
struct sqlite3_index_constraint *p = &pInfo->aConstraint[i];
|
||||
if( p->usable==0 ) continue;
|
||||
if( p->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
|
||||
|
||||
if( p->iColumn==IIC_INDEX_NAME ){
|
||||
iIdxName = i;
|
||||
}
|
||||
if( p->iColumn==IIC_AFTER_KEY ){
|
||||
iAfterKey = i;
|
||||
}
|
||||
}
|
||||
|
||||
if( iIdxName<0 ){
|
||||
pInfo->estimatedCost = 1000000000.0;
|
||||
}else{
|
||||
pInfo->aConstraintUsage[iIdxName].argvIndex = 1;
|
||||
pInfo->aConstraintUsage[iIdxName].omit = 1;
|
||||
if( iAfterKey<0 ){
|
||||
pInfo->estimatedCost = 1000000.0;
|
||||
}else{
|
||||
pInfo->aConstraintUsage[iAfterKey].argvIndex = 2;
|
||||
pInfo->aConstraintUsage[iAfterKey].omit = 1;
|
||||
pInfo->estimatedCost = 1000.0;
|
||||
}
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new btreeinfo cursor.
|
||||
*/
|
||||
static int cidxOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
|
||||
CidxCursor *pRet;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
pRet = cidxMalloc(&rc, sizeof(CidxCursor));
|
||||
|
||||
*ppCursor = (sqlite3_vtab_cursor*)pRet;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a btreeinfo cursor.
|
||||
*/
|
||||
static int cidxClose(sqlite3_vtab_cursor *pCursor){
|
||||
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
||||
sqlite3_finalize(pCsr->pStmt);
|
||||
sqlite3_free(pCsr->zIdxName);
|
||||
sqlite3_free(pCsr->zAfterKey);
|
||||
sqlite3_free(pCsr);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Move a btreeinfo cursor to the next entry in the file.
|
||||
*/
|
||||
static int cidxNext(sqlite3_vtab_cursor *pCursor){
|
||||
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
||||
int rc = sqlite3_step(pCsr->pStmt);
|
||||
if( rc!=SQLITE_ROW ){
|
||||
rc = sqlite3_finalize(pCsr->pStmt);
|
||||
pCsr->pStmt = 0;
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3 *db = ((CidxTable*)pCsr->base.pVtab)->db;
|
||||
cidxCursorError(pCsr, "Cursor error: %s", sqlite3_errmsg(db));
|
||||
}
|
||||
}else{
|
||||
pCsr->iRowid++;
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* We have reached EOF if previous sqlite3_step() returned
|
||||
** anything other than SQLITE_ROW;
|
||||
*/
|
||||
static int cidxEof(sqlite3_vtab_cursor *pCursor){
|
||||
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
||||
return pCsr->pStmt==0;
|
||||
}
|
||||
|
||||
static char *cidxMprintf(int *pRc, const char *zFmt, ...){
|
||||
char *zRet = 0;
|
||||
va_list ap;
|
||||
va_start(ap, zFmt);
|
||||
zRet = sqlite3_vmprintf(zFmt, ap);
|
||||
if( *pRc==SQLITE_OK ){
|
||||
if( zRet==0 ){
|
||||
*pRc = SQLITE_NOMEM;
|
||||
}
|
||||
}else{
|
||||
sqlite3_free(zRet);
|
||||
zRet = 0;
|
||||
}
|
||||
va_end(ap);
|
||||
return zRet;
|
||||
}
|
||||
|
||||
static sqlite3_stmt *cidxPrepare(
|
||||
int *pRc, CidxCursor *pCsr, const char *zFmt, ...
|
||||
){
|
||||
sqlite3_stmt *pRet = 0;
|
||||
char *zSql;
|
||||
va_list ap; /* ... printf arguments */
|
||||
va_start(ap, zFmt);
|
||||
|
||||
zSql = sqlite3_vmprintf(zFmt, ap);
|
||||
if( *pRc==SQLITE_OK ){
|
||||
if( zSql==0 ){
|
||||
*pRc = SQLITE_NOMEM;
|
||||
}else{
|
||||
sqlite3 *db = ((CidxTable*)pCsr->base.pVtab)->db;
|
||||
*pRc = sqlite3_prepare_v2(db, zSql, -1, &pRet, 0);
|
||||
if( *pRc!=SQLITE_OK ){
|
||||
cidxCursorError(pCsr, "SQL error: %s", sqlite3_errmsg(db));
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_free(zSql);
|
||||
va_end(ap);
|
||||
|
||||
return pRet;
|
||||
}
|
||||
|
||||
static void cidxFinalize(int *pRc, sqlite3_stmt *pStmt){
|
||||
int rc = sqlite3_finalize(pStmt);
|
||||
if( *pRc==SQLITE_OK ) *pRc = rc;
|
||||
}
|
||||
|
||||
char *cidxStrdup(int *pRc, const char *zStr){
|
||||
char *zRet = 0;
|
||||
if( *pRc==SQLITE_OK ){
|
||||
int n = (int)strlen(zStr);
|
||||
zRet = cidxMalloc(pRc, n+1);
|
||||
if( zRet ) memcpy(zRet, zStr, n+1);
|
||||
}
|
||||
return zRet;
|
||||
}
|
||||
|
||||
static void cidxFreeIndex(CidxIndex *pIdx){
|
||||
if( pIdx ){
|
||||
int i;
|
||||
for(i=0; i<pIdx->nCol; i++){
|
||||
sqlite3_free(pIdx->aCol[i].zExpr);
|
||||
}
|
||||
sqlite3_free(pIdx->zWhere);
|
||||
sqlite3_free(pIdx);
|
||||
}
|
||||
}
|
||||
|
||||
static int cidx_isspace(char c){
|
||||
return c==' ' || c=='\t' || c=='\r' || c=='\n';
|
||||
}
|
||||
|
||||
static int cidx_isident(char c){
|
||||
return c<0
|
||||
|| (c>='0' && c<='9') || (c>='a' && c<='z')
|
||||
|| (c>='A' && c<='Z') || c=='_';
|
||||
}
|
||||
|
||||
#define CIDX_PARSE_EOF 0
|
||||
#define CIDX_PARSE_COMMA 1 /* "," */
|
||||
#define CIDX_PARSE_OPEN 2 /* "(" */
|
||||
#define CIDX_PARSE_CLOSE 3 /* ")" */
|
||||
|
||||
/*
|
||||
** Argument zIn points into the start, middle or end of a CREATE INDEX
|
||||
** statement. If argument pbDoNotTrim is non-NULL, then this function
|
||||
** scans the input until it finds EOF, a comma (",") or an open or
|
||||
** close parenthesis character. It then sets (*pzOut) to point to said
|
||||
** character and returns a CIDX_PARSE_XXX constant as appropriate. The
|
||||
** parser is smart enough that special characters inside SQL strings
|
||||
** or comments are not returned for.
|
||||
**
|
||||
** Or, if argument pbDoNotTrim is NULL, then this function sets *pzOut
|
||||
** to point to the first character of the string that is not whitespace
|
||||
** or part of an SQL comment and returns CIDX_PARSE_EOF.
|
||||
**
|
||||
** Additionally, if pbDoNotTrim is not NULL and the element immediately
|
||||
** before (*pzOut) is an SQL comment of the form "-- comment", then
|
||||
** (*pbDoNotTrim) is set before returning. In all other cases it is
|
||||
** cleared.
|
||||
*/
|
||||
static int cidxFindNext(
|
||||
const char *zIn,
|
||||
const char **pzOut,
|
||||
int *pbDoNotTrim /* OUT: True if prev is -- comment */
|
||||
){
|
||||
const char *z = zIn;
|
||||
|
||||
while( 1 ){
|
||||
while( cidx_isspace(*z) ) z++;
|
||||
if( z[0]=='-' && z[1]=='-' ){
|
||||
z += 2;
|
||||
while( z[0]!='\n' ){
|
||||
if( z[0]=='\0' ) return CIDX_PARSE_EOF;
|
||||
z++;
|
||||
}
|
||||
while( cidx_isspace(*z) ) z++;
|
||||
if( pbDoNotTrim ) *pbDoNotTrim = 1;
|
||||
}else
|
||||
if( z[0]=='/' && z[1]=='*' ){
|
||||
z += 2;
|
||||
while( z[0]!='*' || z[1]!='/' ){
|
||||
if( z[1]=='\0' ) return CIDX_PARSE_EOF;
|
||||
z++;
|
||||
}
|
||||
z += 2;
|
||||
}else{
|
||||
*pzOut = z;
|
||||
if( pbDoNotTrim==0 ) return CIDX_PARSE_EOF;
|
||||
switch( *z ){
|
||||
case '\0':
|
||||
return CIDX_PARSE_EOF;
|
||||
case '(':
|
||||
return CIDX_PARSE_OPEN;
|
||||
case ')':
|
||||
return CIDX_PARSE_CLOSE;
|
||||
case ',':
|
||||
return CIDX_PARSE_COMMA;
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
case '`': {
|
||||
char q = *z;
|
||||
z++;
|
||||
while( *z ){
|
||||
if( *z==q ){
|
||||
z++;
|
||||
if( *z!=q ) break;
|
||||
}
|
||||
z++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case '[':
|
||||
while( *z++!=']' );
|
||||
break;
|
||||
|
||||
default:
|
||||
z++;
|
||||
break;
|
||||
}
|
||||
*pbDoNotTrim = 0;
|
||||
}
|
||||
}
|
||||
|
||||
assert( 0 );
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int cidxParseSQL(CidxCursor *pCsr, CidxIndex *pIdx, const char *zSql){
|
||||
const char *z = zSql;
|
||||
const char *z1;
|
||||
int e;
|
||||
int rc = SQLITE_OK;
|
||||
int nParen = 1;
|
||||
int bDoNotTrim = 0;
|
||||
CidxColumn *pCol = pIdx->aCol;
|
||||
|
||||
e = cidxFindNext(z, &z, &bDoNotTrim);
|
||||
if( e!=CIDX_PARSE_OPEN ) goto parse_error;
|
||||
z1 = z+1;
|
||||
z++;
|
||||
while( nParen>0 ){
|
||||
e = cidxFindNext(z, &z, &bDoNotTrim);
|
||||
if( e==CIDX_PARSE_EOF ) goto parse_error;
|
||||
if( (e==CIDX_PARSE_COMMA || e==CIDX_PARSE_CLOSE) && nParen==1 ){
|
||||
const char *z2 = z;
|
||||
if( pCol->zExpr ) goto parse_error;
|
||||
|
||||
if( bDoNotTrim==0 ){
|
||||
while( cidx_isspace(z[-1]) ) z--;
|
||||
if( !sqlite3_strnicmp(&z[-3], "asc", 3) && 0==cidx_isident(z[-4]) ){
|
||||
z -= 3;
|
||||
while( cidx_isspace(z[-1]) ) z--;
|
||||
}else
|
||||
if( !sqlite3_strnicmp(&z[-4], "desc", 4) && 0==cidx_isident(z[-5]) ){
|
||||
z -= 4;
|
||||
while( cidx_isspace(z[-1]) ) z--;
|
||||
}
|
||||
while( cidx_isspace(z1[0]) ) z1++;
|
||||
}
|
||||
|
||||
pCol->zExpr = cidxMprintf(&rc, "%.*s", z-z1, z1);
|
||||
pCol++;
|
||||
z = z1 = z2+1;
|
||||
}
|
||||
if( e==CIDX_PARSE_OPEN ) nParen++;
|
||||
if( e==CIDX_PARSE_CLOSE ) nParen--;
|
||||
z++;
|
||||
}
|
||||
|
||||
/* Search for a WHERE clause */
|
||||
cidxFindNext(z, &z, 0);
|
||||
if( 0==sqlite3_strnicmp(z, "where", 5) ){
|
||||
pIdx->zWhere = cidxMprintf(&rc, "%s\n", &z[5]);
|
||||
}else if( z[0]!='\0' ){
|
||||
goto parse_error;
|
||||
}
|
||||
|
||||
return rc;
|
||||
|
||||
parse_error:
|
||||
cidxCursorError(pCsr, "Parse error in: %s", zSql);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
static int cidxLookupIndex(
|
||||
CidxCursor *pCsr, /* Cursor object */
|
||||
const char *zIdx, /* Name of index to look up */
|
||||
CidxIndex **ppIdx, /* OUT: Description of columns */
|
||||
char **pzTab /* OUT: Table name */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
char *zTab = 0;
|
||||
CidxIndex *pIdx = 0;
|
||||
|
||||
sqlite3_stmt *pFindTab = 0;
|
||||
sqlite3_stmt *pInfo = 0;
|
||||
|
||||
/* Find the table for this index. */
|
||||
pFindTab = cidxPrepare(&rc, pCsr,
|
||||
"SELECT tbl_name, sql FROM sqlite_schema WHERE name=%Q AND type='index'",
|
||||
zIdx
|
||||
);
|
||||
if( rc==SQLITE_OK && sqlite3_step(pFindTab)==SQLITE_ROW ){
|
||||
const char *zSql = (const char*)sqlite3_column_text(pFindTab, 1);
|
||||
zTab = cidxStrdup(&rc, (const char*)sqlite3_column_text(pFindTab, 0));
|
||||
|
||||
pInfo = cidxPrepare(&rc, pCsr, "PRAGMA index_xinfo(%Q)", zIdx);
|
||||
if( rc==SQLITE_OK ){
|
||||
int nAlloc = 0;
|
||||
int iCol = 0;
|
||||
|
||||
while( sqlite3_step(pInfo)==SQLITE_ROW ){
|
||||
const char *zName = (const char*)sqlite3_column_text(pInfo, 2);
|
||||
const char *zColl = (const char*)sqlite3_column_text(pInfo, 4);
|
||||
CidxColumn *p;
|
||||
if( zName==0 ) zName = "rowid";
|
||||
if( iCol==nAlloc ){
|
||||
int nByte = sizeof(CidxIndex) + sizeof(CidxColumn)*(nAlloc+8);
|
||||
pIdx = (CidxIndex*)sqlite3_realloc(pIdx, nByte);
|
||||
nAlloc += 8;
|
||||
}
|
||||
p = &pIdx->aCol[iCol++];
|
||||
p->bDesc = sqlite3_column_int(pInfo, 3);
|
||||
p->bKey = sqlite3_column_int(pInfo, 5);
|
||||
if( zSql==0 || p->bKey==0 ){
|
||||
p->zExpr = cidxMprintf(&rc, "\"%w\" COLLATE %s",zName,zColl);
|
||||
}else{
|
||||
p->zExpr = 0;
|
||||
}
|
||||
pIdx->nCol = iCol;
|
||||
pIdx->zWhere = 0;
|
||||
}
|
||||
cidxFinalize(&rc, pInfo);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && zSql ){
|
||||
rc = cidxParseSQL(pCsr, pIdx, zSql);
|
||||
}
|
||||
}
|
||||
|
||||
cidxFinalize(&rc, pFindTab);
|
||||
if( rc==SQLITE_OK && zTab==0 ){
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_free(zTab);
|
||||
cidxFreeIndex(pIdx);
|
||||
}else{
|
||||
*pzTab = zTab;
|
||||
*ppIdx = pIdx;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int cidxDecodeAfter(
|
||||
CidxCursor *pCsr,
|
||||
int nCol,
|
||||
const char *zAfterKey,
|
||||
char ***pazAfter
|
||||
){
|
||||
char **azAfter;
|
||||
int rc = SQLITE_OK;
|
||||
int nAfterKey = (int)strlen(zAfterKey);
|
||||
|
||||
azAfter = cidxMalloc(&rc, sizeof(char*)*nCol + nAfterKey+1);
|
||||
if( rc==SQLITE_OK ){
|
||||
int i;
|
||||
char *zCopy = (char*)&azAfter[nCol];
|
||||
char *p = zCopy;
|
||||
memcpy(zCopy, zAfterKey, nAfterKey+1);
|
||||
for(i=0; i<nCol; i++){
|
||||
while( *p==' ' ) p++;
|
||||
|
||||
/* Check NULL values */
|
||||
if( *p=='N' ){
|
||||
if( memcmp(p, "NULL", 4) ) goto parse_error;
|
||||
p += 4;
|
||||
}
|
||||
|
||||
/* Check strings and blob literals */
|
||||
else if( *p=='X' || *p=='\'' ){
|
||||
azAfter[i] = p;
|
||||
if( *p=='X' ) p++;
|
||||
if( *p!='\'' ) goto parse_error;
|
||||
p++;
|
||||
while( 1 ){
|
||||
if( *p=='\0' ) goto parse_error;
|
||||
if( *p=='\'' ){
|
||||
p++;
|
||||
if( *p!='\'' ) break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check numbers */
|
||||
else{
|
||||
azAfter[i] = p;
|
||||
while( (*p>='0' && *p<='9')
|
||||
|| *p=='.' || *p=='+' || *p=='-' || *p=='e' || *p=='E'
|
||||
){
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
while( *p==' ' ) p++;
|
||||
if( *p!=(i==(nCol-1) ? '\0' : ',') ){
|
||||
goto parse_error;
|
||||
}
|
||||
*p++ = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
*pazAfter = azAfter;
|
||||
return rc;
|
||||
|
||||
parse_error:
|
||||
sqlite3_free(azAfter);
|
||||
*pazAfter = 0;
|
||||
cidxCursorError(pCsr, "%s", "error parsing after value");
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
static char *cidxWhere(
|
||||
int *pRc, CidxColumn *aCol, char **azAfter, int iGt, int bLastIsNull
|
||||
){
|
||||
char *zRet = 0;
|
||||
const char *zSep = "";
|
||||
int i;
|
||||
|
||||
for(i=0; i<iGt; i++){
|
||||
zRet = cidxMprintf(pRc, "%z%s(%s) IS %s", zRet,
|
||||
zSep, aCol[i].zExpr, (azAfter[i] ? azAfter[i] : "NULL")
|
||||
);
|
||||
zSep = " AND ";
|
||||
}
|
||||
|
||||
if( bLastIsNull ){
|
||||
zRet = cidxMprintf(pRc, "%z%s(%s) IS NULL", zRet, zSep, aCol[iGt].zExpr);
|
||||
}
|
||||
else if( azAfter[iGt] ){
|
||||
zRet = cidxMprintf(pRc, "%z%s(%s) %s %s", zRet,
|
||||
zSep, aCol[iGt].zExpr, (aCol[iGt].bDesc ? "<" : ">"),
|
||||
azAfter[iGt]
|
||||
);
|
||||
}else{
|
||||
zRet = cidxMprintf(pRc, "%z%s(%s) IS NOT NULL", zRet, zSep,aCol[iGt].zExpr);
|
||||
}
|
||||
|
||||
return zRet;
|
||||
}
|
||||
|
||||
#define CIDX_CLIST_ALL 0
|
||||
#define CIDX_CLIST_ORDERBY 1
|
||||
#define CIDX_CLIST_CURRENT_KEY 2
|
||||
#define CIDX_CLIST_SUBWHERE 3
|
||||
#define CIDX_CLIST_SUBEXPR 4
|
||||
|
||||
/*
|
||||
** This function returns various strings based on the contents of the
|
||||
** CidxIndex structure and the eType parameter.
|
||||
*/
|
||||
static char *cidxColumnList(
|
||||
int *pRc, /* IN/OUT: Error code */
|
||||
const char *zIdx,
|
||||
CidxIndex *pIdx, /* Indexed columns */
|
||||
int eType /* True to include ASC/DESC */
|
||||
){
|
||||
char *zRet = 0;
|
||||
if( *pRc==SQLITE_OK ){
|
||||
const char *aDir[2] = {"", " DESC"};
|
||||
int i;
|
||||
const char *zSep = "";
|
||||
|
||||
for(i=0; i<pIdx->nCol; i++){
|
||||
CidxColumn *p = &pIdx->aCol[i];
|
||||
assert( pIdx->aCol[i].bDesc==0 || pIdx->aCol[i].bDesc==1 );
|
||||
switch( eType ){
|
||||
|
||||
case CIDX_CLIST_ORDERBY:
|
||||
zRet = cidxMprintf(pRc, "%z%s%d%s", zRet, zSep, i+1, aDir[p->bDesc]);
|
||||
zSep = ",";
|
||||
break;
|
||||
|
||||
case CIDX_CLIST_CURRENT_KEY:
|
||||
zRet = cidxMprintf(pRc, "%z%squote(i%d)", zRet, zSep, i);
|
||||
zSep = "||','||";
|
||||
break;
|
||||
|
||||
case CIDX_CLIST_SUBWHERE:
|
||||
if( p->bKey==0 ){
|
||||
zRet = cidxMprintf(pRc, "%z%s%s IS i.i%d", zRet,
|
||||
zSep, p->zExpr, i
|
||||
);
|
||||
zSep = " AND ";
|
||||
}
|
||||
break;
|
||||
|
||||
case CIDX_CLIST_SUBEXPR:
|
||||
if( p->bKey==1 ){
|
||||
zRet = cidxMprintf(pRc, "%z%s%s IS i.i%d", zRet,
|
||||
zSep, p->zExpr, i
|
||||
);
|
||||
zSep = " AND ";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
assert( eType==CIDX_CLIST_ALL );
|
||||
zRet = cidxMprintf(pRc, "%z%s(%s) AS i%d", zRet, zSep, p->zExpr, i);
|
||||
zSep = ", ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return zRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate SQL (in memory obtained from sqlite3_malloc()) that will
|
||||
** continue the index scan for zIdxName starting after zAfterKey.
|
||||
*/
|
||||
int cidxGenerateScanSql(
|
||||
CidxCursor *pCsr, /* The cursor which needs the new statement */
|
||||
const char *zIdxName, /* index to be scanned */
|
||||
const char *zAfterKey, /* start after this key, if not NULL */
|
||||
char **pzSqlOut /* OUT: Write the generated SQL here */
|
||||
){
|
||||
int rc;
|
||||
char *zTab = 0;
|
||||
char *zCurrentKey = 0;
|
||||
char *zOrderBy = 0;
|
||||
char *zSubWhere = 0;
|
||||
char *zSubExpr = 0;
|
||||
char *zSrcList = 0;
|
||||
char **azAfter = 0;
|
||||
CidxIndex *pIdx = 0;
|
||||
|
||||
*pzSqlOut = 0;
|
||||
rc = cidxLookupIndex(pCsr, zIdxName, &pIdx, &zTab);
|
||||
|
||||
zOrderBy = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_ORDERBY);
|
||||
zCurrentKey = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_CURRENT_KEY);
|
||||
zSubWhere = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_SUBWHERE);
|
||||
zSubExpr = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_SUBEXPR);
|
||||
zSrcList = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_ALL);
|
||||
|
||||
if( rc==SQLITE_OK && zAfterKey ){
|
||||
rc = cidxDecodeAfter(pCsr, pIdx->nCol, zAfterKey, &azAfter);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
if( zAfterKey==0 ){
|
||||
*pzSqlOut = cidxMprintf(&rc,
|
||||
"SELECT (SELECT %s FROM %Q AS t WHERE %s), %s "
|
||||
"FROM (SELECT %s FROM %Q INDEXED BY %Q %s%sORDER BY %s) AS i",
|
||||
zSubExpr, zTab, zSubWhere, zCurrentKey,
|
||||
zSrcList, zTab, zIdxName,
|
||||
(pIdx->zWhere ? "WHERE " : ""), (pIdx->zWhere ? pIdx->zWhere : ""),
|
||||
zOrderBy
|
||||
);
|
||||
}else{
|
||||
const char *zSep = "";
|
||||
char *zSql;
|
||||
int i;
|
||||
|
||||
zSql = cidxMprintf(&rc,
|
||||
"SELECT (SELECT %s FROM %Q WHERE %s), %s FROM (",
|
||||
zSubExpr, zTab, zSubWhere, zCurrentKey
|
||||
);
|
||||
for(i=pIdx->nCol-1; i>=0; i--){
|
||||
int j;
|
||||
if( pIdx->aCol[i].bDesc && azAfter[i]==0 ) continue;
|
||||
for(j=0; j<2; j++){
|
||||
char *zWhere = cidxWhere(&rc, pIdx->aCol, azAfter, i, j);
|
||||
zSql = cidxMprintf(&rc, "%z"
|
||||
"%sSELECT * FROM ("
|
||||
"SELECT %s FROM %Q INDEXED BY %Q WHERE %s%s%z ORDER BY %s"
|
||||
")",
|
||||
zSql, zSep, zSrcList, zTab, zIdxName,
|
||||
pIdx->zWhere ? pIdx->zWhere : "",
|
||||
pIdx->zWhere ? " AND " : "",
|
||||
zWhere, zOrderBy
|
||||
);
|
||||
zSep = " UNION ALL ";
|
||||
if( pIdx->aCol[i].bDesc==0 ) break;
|
||||
}
|
||||
}
|
||||
*pzSqlOut = cidxMprintf(&rc, "%z) AS i", zSql);
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_free(zTab);
|
||||
sqlite3_free(zCurrentKey);
|
||||
sqlite3_free(zOrderBy);
|
||||
sqlite3_free(zSubWhere);
|
||||
sqlite3_free(zSubExpr);
|
||||
sqlite3_free(zSrcList);
|
||||
cidxFreeIndex(pIdx);
|
||||
sqlite3_free(azAfter);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Position a cursor back to the beginning.
|
||||
*/
|
||||
static int cidxFilter(
|
||||
sqlite3_vtab_cursor *pCursor,
|
||||
int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
||||
const char *zIdxName = 0;
|
||||
const char *zAfterKey = 0;
|
||||
|
||||
sqlite3_free(pCsr->zIdxName);
|
||||
pCsr->zIdxName = 0;
|
||||
sqlite3_free(pCsr->zAfterKey);
|
||||
pCsr->zAfterKey = 0;
|
||||
sqlite3_finalize(pCsr->pStmt);
|
||||
pCsr->pStmt = 0;
|
||||
|
||||
if( argc>0 ){
|
||||
zIdxName = (const char*)sqlite3_value_text(argv[0]);
|
||||
if( argc>1 ){
|
||||
zAfterKey = (const char*)sqlite3_value_text(argv[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if( zIdxName ){
|
||||
char *zSql = 0;
|
||||
pCsr->zIdxName = sqlite3_mprintf("%s", zIdxName);
|
||||
pCsr->zAfterKey = zAfterKey ? sqlite3_mprintf("%s", zAfterKey) : 0;
|
||||
rc = cidxGenerateScanSql(pCsr, zIdxName, zAfterKey, &zSql);
|
||||
if( zSql ){
|
||||
pCsr->pStmt = cidxPrepare(&rc, pCsr, "%z", zSql);
|
||||
}
|
||||
}
|
||||
|
||||
if( pCsr->pStmt ){
|
||||
assert( rc==SQLITE_OK );
|
||||
rc = cidxNext(pCursor);
|
||||
}
|
||||
pCsr->iRowid = 1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a column value.
|
||||
*/
|
||||
static int cidxColumn(
|
||||
sqlite3_vtab_cursor *pCursor,
|
||||
sqlite3_context *ctx,
|
||||
int iCol
|
||||
){
|
||||
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
||||
assert( iCol>=IIC_ERRMSG && iCol<=IIC_SCANNER_SQL );
|
||||
switch( iCol ){
|
||||
case IIC_ERRMSG: {
|
||||
const char *zVal = 0;
|
||||
if( sqlite3_column_type(pCsr->pStmt, 0)==SQLITE_INTEGER ){
|
||||
if( sqlite3_column_int(pCsr->pStmt, 0)==0 ){
|
||||
zVal = "row data mismatch";
|
||||
}
|
||||
}else{
|
||||
zVal = "row missing";
|
||||
}
|
||||
sqlite3_result_text(ctx, zVal, -1, SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
case IIC_CURRENT_KEY: {
|
||||
sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pStmt, 1));
|
||||
break;
|
||||
}
|
||||
case IIC_INDEX_NAME: {
|
||||
sqlite3_result_text(ctx, pCsr->zIdxName, -1, SQLITE_TRANSIENT);
|
||||
break;
|
||||
}
|
||||
case IIC_AFTER_KEY: {
|
||||
sqlite3_result_text(ctx, pCsr->zAfterKey, -1, SQLITE_TRANSIENT);
|
||||
break;
|
||||
}
|
||||
case IIC_SCANNER_SQL: {
|
||||
char *zSql = 0;
|
||||
cidxGenerateScanSql(pCsr, pCsr->zIdxName, pCsr->zAfterKey, &zSql);
|
||||
sqlite3_result_text(ctx, zSql, -1, sqlite3_free);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* Return the ROWID for the sqlite_btreeinfo table */
|
||||
static int cidxRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
|
||||
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
||||
*pRowid = pCsr->iRowid;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Register the virtual table modules with the database handle passed
|
||||
** as the only argument.
|
||||
*/
|
||||
static int ciInit(sqlite3 *db){
|
||||
static sqlite3_module cidx_module = {
|
||||
0, /* iVersion */
|
||||
0, /* xCreate */
|
||||
cidxConnect, /* xConnect */
|
||||
cidxBestIndex, /* xBestIndex */
|
||||
cidxDisconnect, /* xDisconnect */
|
||||
0, /* xDestroy */
|
||||
cidxOpen, /* xOpen - open a cursor */
|
||||
cidxClose, /* xClose - close a cursor */
|
||||
cidxFilter, /* xFilter - configure scan constraints */
|
||||
cidxNext, /* xNext - advance a cursor */
|
||||
cidxEof, /* xEof - check for end of scan */
|
||||
cidxColumn, /* xColumn - read data */
|
||||
cidxRowid, /* xRowid - read data */
|
||||
0, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindMethod */
|
||||
0, /* xRename */
|
||||
0, /* xSavepoint */
|
||||
0, /* xRelease */
|
||||
0, /* xRollbackTo */
|
||||
0, /* xShadowName */
|
||||
0 /* xIntegrity */
|
||||
};
|
||||
return sqlite3_create_module(db, "incremental_index_check", &cidx_module, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
** Extension load function.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_checkindex_init(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
return ciInit(db);
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
** Read an SQLite database file and analyze its space utilization. Generate
|
||||
** text on standard output.
|
||||
*/
|
||||
#define TCLSH_INIT_PROC sqlite3_checker_init_proc
|
||||
#define SQLITE_ENABLE_DBPAGE_VTAB 1
|
||||
#undef SQLITE_THREADSAFE
|
||||
#define SQLITE_THREADSAFE 0
|
||||
#undef SQLITE_ENABLE_COLUMN_METADATA
|
||||
#define SQLITE_OMIT_DECLTYPE 1
|
||||
#define SQLITE_OMIT_DEPRECATED 1
|
||||
#define SQLITE_OMIT_PROGRESS_CALLBACK 1
|
||||
#define SQLITE_OMIT_SHARED_CACHE 1
|
||||
#define SQLITE_DEFAULT_MEMSTATUS 0
|
||||
#define SQLITE_MAX_EXPR_DEPTH 0
|
||||
INCLUDE sqlite3.c
|
||||
INCLUDE $ROOT/src/tclsqlite.c
|
||||
INCLUDE $ROOT/ext/misc/btreeinfo.c
|
||||
INCLUDE $ROOT/ext/repair/checkindex.c
|
||||
INCLUDE $ROOT/ext/repair/checkfreelist.c
|
||||
|
||||
/*
|
||||
** Decode a pointer to an sqlite3 object.
|
||||
*/
|
||||
int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb){
|
||||
struct SqliteDb *p;
|
||||
Tcl_CmdInfo cmdInfo;
|
||||
if( Tcl_GetCommandInfo(interp, zA, &cmdInfo) ){
|
||||
p = (struct SqliteDb*)cmdInfo.objClientData;
|
||||
*ppDb = p->db;
|
||||
return TCL_OK;
|
||||
}else{
|
||||
*ppDb = 0;
|
||||
return TCL_ERROR;
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite3_imposter db main rootpage {CREATE TABLE...} ;# setup an imposter
|
||||
** sqlite3_imposter db main ;# rm all imposters
|
||||
*/
|
||||
static int sqlite3_imposter(
|
||||
void *clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3 *db;
|
||||
const char *zSchema;
|
||||
int iRoot;
|
||||
const char *zSql;
|
||||
|
||||
if( objc!=3 && objc!=5 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB SCHEMA [ROOTPAGE SQL]");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
zSchema = Tcl_GetString(objv[2]);
|
||||
if( objc==3 ){
|
||||
sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, zSchema, 0, 1);
|
||||
}else{
|
||||
if( Tcl_GetIntFromObj(interp, objv[3], &iRoot) ) return TCL_ERROR;
|
||||
zSql = Tcl_GetString(objv[4]);
|
||||
sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, zSchema, 1, iRoot);
|
||||
sqlite3_exec(db, zSql, 0, 0, 0);
|
||||
sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, zSchema, 0, 0);
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
const char *sqlite3_checker_init_proc(Tcl_Interp *interp){
|
||||
Tcl_CreateObjCommand(interp, "sqlite3_imposter",
|
||||
(Tcl_ObjCmdProc*)sqlite3_imposter, 0, 0);
|
||||
sqlite3_auto_extension((void(*)(void))sqlite3_btreeinfo_init);
|
||||
sqlite3_auto_extension((void(*)(void))sqlite3_checkindex_init);
|
||||
sqlite3_auto_extension((void(*)(void))sqlite3_checkfreelist_init);
|
||||
return
|
||||
BEGIN_STRING
|
||||
INCLUDE $ROOT/ext/repair/sqlite3_checker.tcl
|
||||
END_STRING
|
||||
;
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
# This TCL script is the main driver script for the sqlite3_checker utility
|
||||
# program.
|
||||
#
|
||||
|
||||
# Special case:
|
||||
#
|
||||
# sqlite3_checker --test FILENAME ARGS
|
||||
#
|
||||
# uses FILENAME in place of this script.
|
||||
#
|
||||
if {[lindex $argv 0]=="--test" && [llength $argv]>1} {
|
||||
set ::argv0 [lindex $argv 1]
|
||||
set argv [lrange $argv 2 end]
|
||||
source $argv0
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Emulate a TCL shell
|
||||
#
|
||||
proc tclsh {} {
|
||||
set line {}
|
||||
while {![eof stdin]} {
|
||||
if {$line!=""} {
|
||||
puts -nonewline "> "
|
||||
} else {
|
||||
puts -nonewline "% "
|
||||
}
|
||||
flush stdout
|
||||
append line [gets stdin]
|
||||
if {[info complete $line]} {
|
||||
if {[catch {uplevel #0 $line} result]} {
|
||||
puts stderr "Error: $result"
|
||||
} elseif {$result!=""} {
|
||||
puts $result
|
||||
}
|
||||
set line {}
|
||||
} else {
|
||||
append line \n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Do an incremental integrity check of a single index
|
||||
#
|
||||
proc check_index {idxname batchsize bTrace} {
|
||||
set i 0
|
||||
set more 1
|
||||
set nerr 0
|
||||
set pct 00.0
|
||||
set max [db one {SELECT nEntry FROM sqlite_btreeinfo('main')
|
||||
WHERE name=$idxname}]
|
||||
puts -nonewline "$idxname: $i of $max rows ($pct%)\r"
|
||||
flush stdout
|
||||
if {$bTrace} {
|
||||
set sql {SELECT errmsg, current_key AS key,
|
||||
CASE WHEN rowid=1 THEN scanner_sql END AS traceOut
|
||||
FROM incremental_index_check($idxname)
|
||||
WHERE after_key=$key
|
||||
LIMIT $batchsize}
|
||||
} else {
|
||||
set sql {SELECT errmsg, current_key AS key, NULL AS traceOut
|
||||
FROM incremental_index_check($idxname)
|
||||
WHERE after_key=$key
|
||||
LIMIT $batchsize}
|
||||
}
|
||||
while {$more} {
|
||||
set more 0
|
||||
db eval $sql {
|
||||
set more 1
|
||||
if {$errmsg!=""} {
|
||||
incr nerr
|
||||
puts "$idxname: key($key): $errmsg"
|
||||
} elseif {$traceOut!=""} {
|
||||
puts "$idxname: $traceOut"
|
||||
}
|
||||
incr i
|
||||
|
||||
}
|
||||
set x [format {%.1f} [expr {($i*100.0)/$max}]]
|
||||
if {$x!=$pct} {
|
||||
puts -nonewline "$idxname: $i of $max rows ($pct%)\r"
|
||||
flush stdout
|
||||
set pct $x
|
||||
}
|
||||
}
|
||||
puts "$idxname: $nerr errors out of $i entries"
|
||||
}
|
||||
|
||||
# Print a usage message on standard error, then quit.
|
||||
#
|
||||
proc usage {} {
|
||||
set argv0 [file rootname [file tail [info nameofexecutable]]]
|
||||
puts stderr "Usage: $argv0 OPTIONS database-filename"
|
||||
puts stderr {
|
||||
Do sanity checking on a live SQLite3 database file specified by the
|
||||
"database-filename" argument.
|
||||
|
||||
Options:
|
||||
|
||||
--batchsize N Number of rows to check per transaction
|
||||
|
||||
--freelist Perform a freelist check
|
||||
|
||||
--index NAME Run a check of the index NAME
|
||||
|
||||
--summary Print summary information about the database
|
||||
|
||||
--table NAME Run a check of all indexes for table NAME
|
||||
|
||||
--tclsh Run the built-in TCL interpreter (for debugging)
|
||||
|
||||
--trace (Debugging only:) Output trace information on the scan
|
||||
|
||||
--version Show the version number of SQLite
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
set file_to_analyze {}
|
||||
append argv {}
|
||||
set bFreelistCheck 0
|
||||
set bSummary 0
|
||||
set zIndex {}
|
||||
set zTable {}
|
||||
set batchsize 1000
|
||||
set bAll 1
|
||||
set bTrace 0
|
||||
set argc [llength $argv]
|
||||
for {set i 0} {$i<$argc} {incr i} {
|
||||
set arg [lindex $argv $i]
|
||||
if {[regexp {^-+tclsh$} $arg]} {
|
||||
tclsh
|
||||
exit 0
|
||||
}
|
||||
if {[regexp {^-+version$} $arg]} {
|
||||
sqlite3 mem :memory:
|
||||
puts [mem one {SELECT sqlite_version()||' '||sqlite_source_id()}]
|
||||
mem close
|
||||
exit 0
|
||||
}
|
||||
if {[regexp {^-+freelist$} $arg]} {
|
||||
set bFreelistCheck 1
|
||||
set bAll 0
|
||||
continue
|
||||
}
|
||||
if {[regexp {^-+summary$} $arg]} {
|
||||
set bSummary 1
|
||||
set bAll 0
|
||||
continue
|
||||
}
|
||||
if {[regexp {^-+trace$} $arg]} {
|
||||
set bTrace 1
|
||||
continue
|
||||
}
|
||||
if {[regexp {^-+batchsize$} $arg]} {
|
||||
incr i
|
||||
if {$i>=$argc} {
|
||||
puts stderr "missing argument on $arg"
|
||||
exit 1
|
||||
}
|
||||
set batchsize [lindex $argv $i]
|
||||
continue
|
||||
}
|
||||
if {[regexp {^-+index$} $arg]} {
|
||||
incr i
|
||||
if {$i>=$argc} {
|
||||
puts stderr "missing argument on $arg"
|
||||
exit 1
|
||||
}
|
||||
set zIndex [lindex $argv $i]
|
||||
set bAll 0
|
||||
continue
|
||||
}
|
||||
if {[regexp {^-+table$} $arg]} {
|
||||
incr i
|
||||
if {$i>=$argc} {
|
||||
puts stderr "missing argument on $arg"
|
||||
exit 1
|
||||
}
|
||||
set zTable [lindex $argv $i]
|
||||
set bAll 0
|
||||
continue
|
||||
}
|
||||
if {[regexp {^-} $arg]} {
|
||||
puts stderr "Unknown option: $arg"
|
||||
usage
|
||||
}
|
||||
if {$file_to_analyze!=""} {
|
||||
usage
|
||||
} else {
|
||||
set file_to_analyze $arg
|
||||
}
|
||||
}
|
||||
if {$file_to_analyze==""} usage
|
||||
|
||||
# If a TCL script is specified on the command-line, then run that
|
||||
# script.
|
||||
#
|
||||
if {[file extension $file_to_analyze]==".tcl"} {
|
||||
source $file_to_analyze
|
||||
exit 0
|
||||
}
|
||||
|
||||
set root_filename $file_to_analyze
|
||||
regexp {^file:(//)?([^?]*)} $file_to_analyze all x1 root_filename
|
||||
if {![file exists $root_filename]} {
|
||||
puts stderr "No such file: $root_filename"
|
||||
exit 1
|
||||
}
|
||||
if {![file readable $root_filename]} {
|
||||
puts stderr "File is not readable: $root_filename"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if {[catch {sqlite3 db $file_to_analyze} res]} {
|
||||
puts stderr "Cannot open datababase $root_filename: $res"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if {$bFreelistCheck || $bAll} {
|
||||
puts -nonewline "freelist-check: "
|
||||
flush stdout
|
||||
db eval BEGIN
|
||||
puts [db one {SELECT checkfreelist('main')}]
|
||||
db eval END
|
||||
}
|
||||
if {$bSummary} {
|
||||
set scale 0
|
||||
set pgsz [db one {PRAGMA page_size}]
|
||||
db eval {SELECT nPage*$pgsz AS sz, name, tbl_name
|
||||
FROM sqlite_btreeinfo
|
||||
WHERE type='index'
|
||||
ORDER BY 1 DESC, name} {
|
||||
if {$scale==0} {
|
||||
if {$sz>10000000} {
|
||||
set scale 1000000.0
|
||||
set unit MB
|
||||
} else {
|
||||
set scale 1000.0
|
||||
set unit KB
|
||||
}
|
||||
}
|
||||
puts [format {%7.1f %s index %s of table %s} \
|
||||
[expr {$sz/$scale}] $unit $name $tbl_name]
|
||||
}
|
||||
}
|
||||
if {$zIndex!=""} {
|
||||
check_index $zIndex $batchsize $bTrace
|
||||
}
|
||||
if {$zTable!=""} {
|
||||
foreach idx [db eval {SELECT name FROM sqlite_master
|
||||
WHERE type='index' AND rootpage>0
|
||||
AND tbl_name=$zTable}] {
|
||||
check_index $idx $batchsize $bTrace
|
||||
}
|
||||
}
|
||||
if {$bAll} {
|
||||
set allidx [db eval {SELECT name FROM sqlite_btreeinfo('main')
|
||||
WHERE type='index' AND rootpage>0
|
||||
ORDER BY nEntry}]
|
||||
foreach idx $allidx {
|
||||
check_index $idx $batchsize $bTrace
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
To run these tests, first build sqlite3_checker:
|
||||
|
||||
|
||||
> make sqlite3_checker
|
||||
|
||||
|
||||
Then run the "test.tcl" script using:
|
||||
|
||||
|
||||
> ./sqlite3_checker --test $path/test.tcl
|
||||
|
||||
|
||||
Optionally add the full pathnames of individual *.test modules
|
||||
@@ -1,92 +0,0 @@
|
||||
# 2017-10-11
|
||||
|
||||
set testprefix checkfreelist
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA page_size=1024;
|
||||
CREATE TABLE t1(a, b);
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 { SELECT checkfreelist('main') } {ok}
|
||||
do_execsql_test 1.3 {
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10000
|
||||
)
|
||||
INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM s;
|
||||
DELETE FROM t1 WHERE rowid%3;
|
||||
PRAGMA freelist_count;
|
||||
} {6726}
|
||||
|
||||
do_execsql_test 1.4 { SELECT checkfreelist('main') } {ok}
|
||||
do_execsql_test 1.5 {
|
||||
WITH freelist_trunk(i, d, n) AS (
|
||||
SELECT 1, NULL, sqlite_readint32(data, 32) FROM sqlite_dbpage WHERE pgno=1
|
||||
UNION ALL
|
||||
SELECT n, data, sqlite_readint32(data)
|
||||
FROM freelist_trunk, sqlite_dbpage WHERE pgno=n
|
||||
)
|
||||
SELECT i FROM freelist_trunk WHERE i!=1;
|
||||
} {
|
||||
10009 9715 9343 8969 8595 8222 7847 7474 7102 6727 6354 5982 5608 5234
|
||||
4860 4487 4112 3740 3367 2992 2619 2247 1872 1499 1125 752 377 5
|
||||
}
|
||||
|
||||
do_execsql_test 1.6 { SELECT checkfreelist('main') } {ok}
|
||||
|
||||
proc set_int {blob idx newval} {
|
||||
binary scan $blob I* ints
|
||||
lset ints $idx $newval
|
||||
binary format I* $ints
|
||||
}
|
||||
db func set_int set_int
|
||||
|
||||
proc get_int {blob idx} {
|
||||
binary scan $blob I* ints
|
||||
lindex $ints $idx
|
||||
}
|
||||
db func get_int get_int
|
||||
|
||||
do_execsql_test 1.7 {
|
||||
BEGIN;
|
||||
UPDATE sqlite_dbpage
|
||||
SET data = set_int(data, 1, get_int(data, 1)-1)
|
||||
WHERE pgno=4860;
|
||||
SELECT checkfreelist('main');
|
||||
ROLLBACK;
|
||||
} {{free-list count mismatch: actual=6725 header=6726}}
|
||||
|
||||
do_execsql_test 1.8 {
|
||||
BEGIN;
|
||||
UPDATE sqlite_dbpage
|
||||
SET data = set_int(data, 5, (SELECT * FROM pragma_page_count)+1)
|
||||
WHERE pgno=4860;
|
||||
SELECT checkfreelist('main');
|
||||
ROLLBACK;
|
||||
} {{leaf page 10092 is out of range (child 3 of trunk page 4860)}}
|
||||
|
||||
do_execsql_test 1.9 {
|
||||
BEGIN;
|
||||
UPDATE sqlite_dbpage
|
||||
SET data = set_int(data, 5, 0)
|
||||
WHERE pgno=4860;
|
||||
SELECT checkfreelist('main');
|
||||
ROLLBACK;
|
||||
} {{leaf page 0 is out of range (child 3 of trunk page 4860)}}
|
||||
|
||||
do_execsql_test 1.10 {
|
||||
BEGIN;
|
||||
UPDATE sqlite_dbpage
|
||||
SET data = set_int(data, get_int(data, 1)+1, 0)
|
||||
WHERE pgno=5;
|
||||
SELECT checkfreelist('main');
|
||||
ROLLBACK;
|
||||
} {{leaf page 0 is out of range (child 247 of trunk page 5)}}
|
||||
|
||||
do_execsql_test 1.11 {
|
||||
BEGIN;
|
||||
UPDATE sqlite_dbpage
|
||||
SET data = set_int(data, 1, 249)
|
||||
WHERE pgno=5;
|
||||
SELECT checkfreelist('main');
|
||||
ROLLBACK;
|
||||
} {{leaf count out of range (249) on trunk page 5}}
|
||||
@@ -1,349 +0,0 @@
|
||||
# 2017-10-11
|
||||
#
|
||||
set testprefix checkindex
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE INDEX i1 ON t1(a);
|
||||
INSERT INTO t1 VALUES('one', 2);
|
||||
INSERT INTO t1 VALUES('two', 4);
|
||||
INSERT INTO t1 VALUES('three', 6);
|
||||
INSERT INTO t1 VALUES('four', 8);
|
||||
INSERT INTO t1 VALUES('five', 10);
|
||||
|
||||
CREATE INDEX i2 ON t1(a DESC);
|
||||
} {}
|
||||
|
||||
proc incr_index_check {idx nStep} {
|
||||
set Q {
|
||||
SELECT errmsg, current_key FROM incremental_index_check($idx, $after)
|
||||
LIMIT $nStep
|
||||
}
|
||||
|
||||
set res [list]
|
||||
while {1} {
|
||||
unset -nocomplain current_key
|
||||
set res1 [db eval $Q]
|
||||
if {[llength $res1]==0} break
|
||||
set res [concat $res $res1]
|
||||
set after [lindex $res end]
|
||||
}
|
||||
|
||||
return $res
|
||||
}
|
||||
|
||||
proc do_index_check_test {tn idx res} {
|
||||
uplevel [list do_execsql_test $tn.1 "
|
||||
SELECT errmsg, current_key FROM incremental_index_check('$idx');
|
||||
" $res]
|
||||
|
||||
uplevel [list do_test $tn.2 "incr_index_check $idx 1" [list {*}$res]]
|
||||
uplevel [list do_test $tn.3 "incr_index_check $idx 2" [list {*}$res]]
|
||||
uplevel [list do_test $tn.4 "incr_index_check $idx 5" [list {*}$res]]
|
||||
}
|
||||
|
||||
|
||||
do_execsql_test 1.2.1 {
|
||||
SELECT rowid, errmsg IS NULL, current_key FROM incremental_index_check('i1');
|
||||
} {
|
||||
1 1 'five',5
|
||||
2 1 'four',4
|
||||
3 1 'one',1
|
||||
4 1 'three',3
|
||||
5 1 'two',2
|
||||
}
|
||||
do_execsql_test 1.2.2 {
|
||||
SELECT errmsg IS NULL, current_key, index_name, after_key, scanner_sql
|
||||
FROM incremental_index_check('i1') LIMIT 1;
|
||||
} {
|
||||
1
|
||||
'five',5
|
||||
i1
|
||||
{}
|
||||
{SELECT (SELECT a IS i.i0 FROM 't1' AS t WHERE "rowid" COLLATE BINARY IS i.i1), quote(i0)||','||quote(i1) FROM (SELECT (a) AS i0, ("rowid" COLLATE BINARY) AS i1 FROM 't1' INDEXED BY 'i1' ORDER BY 1,2) AS i}
|
||||
}
|
||||
|
||||
do_index_check_test 1.3 i1 {
|
||||
{} 'five',5
|
||||
{} 'four',4
|
||||
{} 'one',1
|
||||
{} 'three',3
|
||||
{} 'two',2
|
||||
}
|
||||
|
||||
do_index_check_test 1.4 i2 {
|
||||
{} 'two',2
|
||||
{} 'three',3
|
||||
{} 'one',1
|
||||
{} 'four',4
|
||||
{} 'five',5
|
||||
}
|
||||
|
||||
do_test 1.5 {
|
||||
set tblroot [db one { SELECT rootpage FROM sqlite_master WHERE name='t1' }]
|
||||
sqlite3_imposter db main $tblroot {CREATE TABLE xt1(a,b)}
|
||||
db eval {
|
||||
UPDATE xt1 SET a='six' WHERE rowid=3;
|
||||
DELETE FROM xt1 WHERE rowid = 5;
|
||||
}
|
||||
sqlite3_imposter db main
|
||||
} {}
|
||||
|
||||
do_index_check_test 1.6 i1 {
|
||||
{row missing} 'five',5
|
||||
{} 'four',4
|
||||
{} 'one',1
|
||||
{row data mismatch} 'three',3
|
||||
{} 'two',2
|
||||
}
|
||||
|
||||
do_index_check_test 1.7 i2 {
|
||||
{} 'two',2
|
||||
{row data mismatch} 'three',3
|
||||
{} 'one',1
|
||||
{} 'four',4
|
||||
{row missing} 'five',5
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
do_execsql_test 2.0 {
|
||||
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c, d);
|
||||
|
||||
INSERT INTO t2 VALUES(1, NULL, 1, 1);
|
||||
INSERT INTO t2 VALUES(2, 1, NULL, 1);
|
||||
INSERT INTO t2 VALUES(3, 1, 1, NULL);
|
||||
|
||||
INSERT INTO t2 VALUES(4, 2, 2, 1);
|
||||
INSERT INTO t2 VALUES(5, 2, 2, 2);
|
||||
INSERT INTO t2 VALUES(6, 2, 2, 3);
|
||||
|
||||
INSERT INTO t2 VALUES(7, 2, 2, 1);
|
||||
INSERT INTO t2 VALUES(8, 2, 2, 2);
|
||||
INSERT INTO t2 VALUES(9, 2, 2, 3);
|
||||
|
||||
CREATE INDEX i3 ON t2(b, c, d);
|
||||
CREATE INDEX i4 ON t2(b DESC, c DESC, d DESC);
|
||||
CREATE INDEX i5 ON t2(d, c DESC, b);
|
||||
} {}
|
||||
|
||||
do_index_check_test 2.1 i3 {
|
||||
{} NULL,1,1,1
|
||||
{} 1,NULL,1,2
|
||||
{} 1,1,NULL,3
|
||||
{} 2,2,1,4
|
||||
{} 2,2,1,7
|
||||
{} 2,2,2,5
|
||||
{} 2,2,2,8
|
||||
{} 2,2,3,6
|
||||
{} 2,2,3,9
|
||||
}
|
||||
|
||||
do_index_check_test 2.2 i4 {
|
||||
{} 2,2,3,6
|
||||
{} 2,2,3,9
|
||||
{} 2,2,2,5
|
||||
{} 2,2,2,8
|
||||
{} 2,2,1,4
|
||||
{} 2,2,1,7
|
||||
{} 1,1,NULL,3
|
||||
{} 1,NULL,1,2
|
||||
{} NULL,1,1,1
|
||||
}
|
||||
|
||||
do_index_check_test 2.3 i5 {
|
||||
{} NULL,1,1,3
|
||||
{} 1,2,2,4
|
||||
{} 1,2,2,7
|
||||
{} 1,1,NULL,1
|
||||
{} 1,NULL,1,2
|
||||
{} 2,2,2,5
|
||||
{} 2,2,2,8
|
||||
{} 3,2,2,6
|
||||
{} 3,2,2,9
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
do_execsql_test 3.0 {
|
||||
|
||||
CREATE TABLE t3(w, x, y, z PRIMARY KEY) WITHOUT ROWID;
|
||||
CREATE INDEX t3wxy ON t3(w, x, y);
|
||||
CREATE INDEX t3wxy2 ON t3(w DESC, x DESC, y DESC);
|
||||
|
||||
INSERT INTO t3 VALUES(NULL, NULL, NULL, 1);
|
||||
INSERT INTO t3 VALUES(NULL, NULL, NULL, 2);
|
||||
INSERT INTO t3 VALUES(NULL, NULL, NULL, 3);
|
||||
|
||||
INSERT INTO t3 VALUES('a', NULL, NULL, 4);
|
||||
INSERT INTO t3 VALUES('a', NULL, NULL, 5);
|
||||
INSERT INTO t3 VALUES('a', NULL, NULL, 6);
|
||||
|
||||
INSERT INTO t3 VALUES('a', 'b', NULL, 7);
|
||||
INSERT INTO t3 VALUES('a', 'b', NULL, 8);
|
||||
INSERT INTO t3 VALUES('a', 'b', NULL, 9);
|
||||
|
||||
} {}
|
||||
|
||||
do_index_check_test 3.1 t3wxy {
|
||||
{} NULL,NULL,NULL,1 {} NULL,NULL,NULL,2 {} NULL,NULL,NULL,3
|
||||
{} 'a',NULL,NULL,4 {} 'a',NULL,NULL,5 {} 'a',NULL,NULL,6
|
||||
{} 'a','b',NULL,7 {} 'a','b',NULL,8 {} 'a','b',NULL,9
|
||||
}
|
||||
do_index_check_test 3.2 t3wxy2 {
|
||||
{} 'a','b',NULL,7 {} 'a','b',NULL,8 {} 'a','b',NULL,9
|
||||
{} 'a',NULL,NULL,4 {} 'a',NULL,NULL,5 {} 'a',NULL,NULL,6
|
||||
{} NULL,NULL,NULL,1 {} NULL,NULL,NULL,2 {} NULL,NULL,NULL,3
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Test with an index that uses non-default collation sequences.
|
||||
#
|
||||
do_execsql_test 4.0 {
|
||||
CREATE TABLE t4(a INTEGER PRIMARY KEY, c1 TEXT, c2 TEXT);
|
||||
INSERT INTO t4 VALUES(1, 'aaa', 'bbb');
|
||||
INSERT INTO t4 VALUES(2, 'AAA', 'CCC');
|
||||
INSERT INTO t4 VALUES(3, 'aab', 'ddd');
|
||||
INSERT INTO t4 VALUES(4, 'AAB', 'EEE');
|
||||
|
||||
CREATE INDEX t4cc ON t4(c1 COLLATE nocase, c2 COLLATE nocase);
|
||||
}
|
||||
|
||||
do_index_check_test 4.1 t4cc {
|
||||
{} 'aaa','bbb',1
|
||||
{} 'AAA','CCC',2
|
||||
{} 'aab','ddd',3
|
||||
{} 'AAB','EEE',4
|
||||
}
|
||||
|
||||
do_test 4.2 {
|
||||
set tblroot [db one { SELECT rootpage FROM sqlite_master WHERE name='t4' }]
|
||||
sqlite3_imposter db main $tblroot \
|
||||
{CREATE TABLE xt4(a INTEGER PRIMARY KEY, c1 TEXT, c2 TEXT)}
|
||||
|
||||
db eval {
|
||||
UPDATE xt4 SET c1='hello' WHERE rowid=2;
|
||||
DELETE FROM xt4 WHERE rowid = 3;
|
||||
}
|
||||
sqlite3_imposter db main
|
||||
} {}
|
||||
|
||||
do_index_check_test 4.3 t4cc {
|
||||
{} 'aaa','bbb',1
|
||||
{row data mismatch} 'AAA','CCC',2
|
||||
{row missing} 'aab','ddd',3
|
||||
{} 'AAB','EEE',4
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Test an index on an expression.
|
||||
#
|
||||
do_execsql_test 5.0 {
|
||||
CREATE TABLE t5(x INTEGER PRIMARY KEY, y TEXT, UNIQUE(y));
|
||||
INSERT INTO t5 VALUES(1, '{"x":1, "y":1}');
|
||||
INSERT INTO t5 VALUES(2, '{"x":2, "y":2}');
|
||||
INSERT INTO t5 VALUES(3, '{"x":3, "y":3}');
|
||||
INSERT INTO t5 VALUES(4, '{"w":4, "z":4}');
|
||||
INSERT INTO t5 VALUES(5, '{"x":5, "y":5}');
|
||||
|
||||
CREATE INDEX t5x ON t5( json_extract(y, '$.x') );
|
||||
CREATE INDEX t5y ON t5( json_extract(y, '$.y') DESC );
|
||||
}
|
||||
|
||||
do_index_check_test 5.1.1 t5x {
|
||||
{} NULL,4 {} 1,1 {} 2,2 {} 3,3 {} 5,5
|
||||
}
|
||||
|
||||
do_index_check_test 5.1.2 t5y {
|
||||
{} 5,5 {} 3,3 {} 2,2 {} 1,1 {} NULL,4
|
||||
}
|
||||
|
||||
do_index_check_test 5.1.3 sqlite_autoindex_t5_1 {
|
||||
{} {'{"w":4, "z":4}',4}
|
||||
{} {'{"x":1, "y":1}',1}
|
||||
{} {'{"x":2, "y":2}',2}
|
||||
{} {'{"x":3, "y":3}',3}
|
||||
{} {'{"x":5, "y":5}',5}
|
||||
}
|
||||
|
||||
do_test 5.2 {
|
||||
set tblroot [db one { SELECT rootpage FROM sqlite_master WHERE name='t5' }]
|
||||
sqlite3_imposter db main $tblroot \
|
||||
{CREATE TABLE xt5(a INTEGER PRIMARY KEY, c1 TEXT);}
|
||||
db eval {
|
||||
UPDATE xt5 SET c1='{"x":22, "y":11}' WHERE rowid=1;
|
||||
DELETE FROM xt5 WHERE rowid = 4;
|
||||
}
|
||||
sqlite3_imposter db main
|
||||
} {}
|
||||
|
||||
do_index_check_test 5.3.1 t5x {
|
||||
{row missing} NULL,4
|
||||
{row data mismatch} 1,1
|
||||
{} 2,2
|
||||
{} 3,3
|
||||
{} 5,5
|
||||
}
|
||||
|
||||
do_index_check_test 5.3.2 sqlite_autoindex_t5_1 {
|
||||
{row missing} {'{"w":4, "z":4}',4}
|
||||
{row data mismatch} {'{"x":1, "y":1}',1}
|
||||
{} {'{"x":2, "y":2}',2}
|
||||
{} {'{"x":3, "y":3}',3}
|
||||
{} {'{"x":5, "y":5}',5}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_execsql_test 6.0 {
|
||||
CREATE TABLE t6(x INTEGER PRIMARY KEY, y, z);
|
||||
CREATE INDEX t6x1 ON t6(y, /* one,two,three */ z);
|
||||
CREATE INDEX t6x2 ON t6(z, -- hello,world,
|
||||
y);
|
||||
|
||||
CREATE INDEX t6x3 ON t6(z -- hello,world
|
||||
, y);
|
||||
|
||||
INSERT INTO t6 VALUES(1, 2, 3);
|
||||
INSERT INTO t6 VALUES(4, 5, 6);
|
||||
}
|
||||
|
||||
do_index_check_test 6.1 t6x1 {
|
||||
{} 2,3,1
|
||||
{} 5,6,4
|
||||
}
|
||||
do_index_check_test 6.2 t6x2 {
|
||||
{} 3,2,1
|
||||
{} 6,5,4
|
||||
}
|
||||
do_index_check_test 6.2 t6x3 {
|
||||
{} 3,2,1
|
||||
{} 6,5,4
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_execsql_test 7.0 {
|
||||
CREATE TABLE t7(x INTEGER PRIMARY KEY, y, z);
|
||||
INSERT INTO t7 VALUES(1, 1, 1);
|
||||
INSERT INTO t7 VALUES(2, 2, 0);
|
||||
INSERT INTO t7 VALUES(3, 3, 1);
|
||||
INSERT INTO t7 VALUES(4, 4, 0);
|
||||
|
||||
CREATE INDEX t7i1 ON t7(y) WHERE z=1;
|
||||
CREATE INDEX t7i2 ON t7(y) /* hello,world */ WHERE z=1;
|
||||
CREATE INDEX t7i3 ON t7(y) WHERE -- yep
|
||||
z=1;
|
||||
CREATE INDEX t7i4 ON t7(y) WHERE z=1 -- yep;
|
||||
}
|
||||
do_index_check_test 7.1 t7i1 {
|
||||
{} 1,1 {} 3,3
|
||||
}
|
||||
do_index_check_test 7.2 t7i2 {
|
||||
{} 1,1 {} 3,3
|
||||
}
|
||||
do_index_check_test 7.3 t7i3 {
|
||||
{} 1,1 {} 3,3
|
||||
}
|
||||
do_index_check_test 7.4 t7i4 {
|
||||
{} 1,1 {} 3,3
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
# Run this script using
|
||||
#
|
||||
# sqlite3_checker --test $thisscript $testscripts
|
||||
#
|
||||
# The $testscripts argument is optional. If omitted, all *.test files
|
||||
# in the same directory as $thisscript are run.
|
||||
#
|
||||
set NTEST 0
|
||||
set NERR 0
|
||||
|
||||
|
||||
# Invoke the do_test procedure to run a single test
|
||||
#
|
||||
# The $expected parameter is the expected result. The result is the return
|
||||
# value from the last TCL command in $cmd.
|
||||
#
|
||||
# Normally, $expected must match exactly. But if $expected is of the form
|
||||
# "/regexp/" then regular expression matching is used. If $expected is
|
||||
# "~/regexp/" then the regular expression must NOT match. If $expected is
|
||||
# of the form "#/value-list/" then each term in value-list must be numeric
|
||||
# and must approximately match the corresponding numeric term in $result.
|
||||
# Values must match within 10%. Or if the $expected term is A..B then the
|
||||
# $result term must be in between A and B.
|
||||
#
|
||||
proc do_test {name cmd expected} {
|
||||
if {[info exists ::testprefix]} {
|
||||
set name "$::testprefix$name"
|
||||
}
|
||||
|
||||
incr ::NTEST
|
||||
puts -nonewline $name...
|
||||
flush stdout
|
||||
|
||||
if {[catch {uplevel #0 "$cmd;\n"} result]} {
|
||||
puts -nonewline $name...
|
||||
puts "\nError: $result"
|
||||
incr ::NERR
|
||||
} else {
|
||||
set ok [expr {[string compare $result $expected]==0}]
|
||||
if {!$ok} {
|
||||
puts "\n! $name expected: \[$expected\]\n! $name got: \[$result\]"
|
||||
incr ::NERR
|
||||
} else {
|
||||
puts " Ok"
|
||||
}
|
||||
}
|
||||
flush stdout
|
||||
}
|
||||
|
||||
#
|
||||
# do_execsql_test TESTNAME SQL RES
|
||||
#
|
||||
proc do_execsql_test {testname sql {result {}}} {
|
||||
uplevel [list do_test $testname [list db eval $sql] [list {*}$result]]
|
||||
}
|
||||
|
||||
if {[llength $argv]==0} {
|
||||
set dir [file dirname $argv0]
|
||||
set argv [glob -nocomplain $dir/*.test]
|
||||
}
|
||||
foreach testfile $argv {
|
||||
file delete -force test.db
|
||||
sqlite3 db test.db
|
||||
source $testfile
|
||||
catch {db close}
|
||||
}
|
||||
puts "$NERR errors out of $NTEST tests"
|
||||
+1
-1
@@ -200,7 +200,7 @@ static int geopolyParseNumber(GeoParse *p, GeoCoord *pVal){
|
||||
/* The sqlite3AtoF() routine is much much faster than atof(), if it
|
||||
** is available */
|
||||
double r;
|
||||
(void)sqlite3AtoF((const char*)p->z, &r, j, SQLITE_UTF8);
|
||||
(void)sqlite3AtoF((const char*)p->z, &r);
|
||||
*pVal = r;
|
||||
#else
|
||||
*pVal = (GeoCoord)atof((const char*)p->z);
|
||||
|
||||
+13
-12
@@ -1037,7 +1037,17 @@ static void rtreeRelease(Rtree *pRtree){
|
||||
pRtree->inWrTrans = 0;
|
||||
assert( pRtree->nCursor==0 );
|
||||
nodeBlobReset(pRtree);
|
||||
assert( pRtree->nNodeRef==0 || pRtree->bCorrupt );
|
||||
if( pRtree->nNodeRef ){
|
||||
int i;
|
||||
assert( pRtree->bCorrupt );
|
||||
for(i=0; i<HASHSIZE; i++){
|
||||
while( pRtree->aHash[i] ){
|
||||
RtreeNode *pNext = pRtree->aHash[i]->pNext;
|
||||
sqlite3_free(pRtree->aHash[i]);
|
||||
pRtree->aHash[i] = pNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(pRtree->pWriteNode);
|
||||
sqlite3_finalize(pRtree->pDeleteNode);
|
||||
sqlite3_finalize(pRtree->pReadRowid);
|
||||
@@ -2329,7 +2339,7 @@ static int AdjustTree(
|
||||
int iCell;
|
||||
|
||||
cnt++;
|
||||
if( NEVER(cnt>100) ){
|
||||
if( cnt>100 ){
|
||||
RTREE_IS_CORRUPT(pRtree);
|
||||
return SQLITE_CORRUPT_VTAB;
|
||||
}
|
||||
@@ -2687,15 +2697,6 @@ static int SplitNode(
|
||||
rc = updateMapping(pRtree, pCell->iRowid, pLeft, iHeight);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = nodeRelease(pRtree, pRight);
|
||||
pRight = 0;
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = nodeRelease(pRtree, pLeft);
|
||||
pLeft = 0;
|
||||
}
|
||||
|
||||
splitnode_out:
|
||||
nodeRelease(pRtree, pRight);
|
||||
nodeRelease(pRtree, pLeft);
|
||||
@@ -2880,7 +2881,7 @@ static int rtreeInsertCell(
|
||||
rc = SplitNode(pRtree, pNode, pCell, iHeight);
|
||||
}else{
|
||||
rc = AdjustTree(pRtree, pNode, pCell);
|
||||
if( ALWAYS(rc==SQLITE_OK) ){
|
||||
if( rc==SQLITE_OK ){
|
||||
if( iHeight==0 ){
|
||||
rc = rowidWrite(pRtree, pCell->iRowid, pNode->iNode);
|
||||
}else{
|
||||
|
||||
@@ -195,13 +195,70 @@ do_test 3.3 {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
set C [binary format c* 0x54 0x01 0x01 0x00 0x12 0x00 0x05]
|
||||
set C [binary format c* {0x54 0x01 0x01 0x00 0x12 0x00 0x05}]
|
||||
|
||||
do_test 4.0 {
|
||||
sqlite3changegroup grp
|
||||
list [catch { grp add $C } msg] $msg
|
||||
} {1 SQLITE_CORRUPT}
|
||||
} {0 {}}
|
||||
grp delete
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
set C [binary format c* {0x54 0xda 0xda 0xda 0xda 0xda}]
|
||||
|
||||
do_execsql_test 5.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b, c, d);
|
||||
}
|
||||
|
||||
do_test 5.1 {
|
||||
list [catch { sqlite3changeset_apply db $C noop xFilter } msg] $msg
|
||||
} {1 SQLITE_CORRUPT}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
|
||||
foreach {tn type C2hex C3hex} {
|
||||
1 changeset 54020100743100170001000000000000000A030374656E000306656C6576656E
|
||||
54020100743100170001000000000000000A030374656E0003FFFF01656C6576656E
|
||||
|
||||
2 patchset 50020100743100170001000000000000000A0306656C6576656E
|
||||
50020100743100170001000000000000000A03FFFF06656C6576656E
|
||||
|
||||
3 changeset 54020100743100170001000000000000000A030374656E000306656C6576656E
|
||||
54020100743100170001000000000000000A030374656E0003FFFF
|
||||
} {
|
||||
reset_db
|
||||
|
||||
do_execsql_test 6.$tn.0 {
|
||||
CREATE TABLE t1(x INTEGER PRIMARY KEY, y TEXT);
|
||||
INSERT INTO t1 VALUES(1, 'one');
|
||||
INSERT INTO t1 VALUES(2, 'two');
|
||||
INSERT INTO t1 VALUES(3, 'three');
|
||||
} {}
|
||||
|
||||
do_test 6.$tn.1 {
|
||||
set C1 [${type}_from_sql {
|
||||
INSERT INTO t1 VALUES(10, 'ten');
|
||||
}]
|
||||
set C2 [${type}_from_sql {
|
||||
UPDATE t1 SET y='eleven' WHERE x=10;
|
||||
}]
|
||||
db one { SELECT quote($C2) }
|
||||
} "X'$C2hex'"
|
||||
|
||||
do_test 6.$tn.2 {
|
||||
changeset_to_list [sqlite3changeset_concat $C1 $C2]
|
||||
} {{INSERT t1 0 X. {} {i 10 t eleven}}}
|
||||
|
||||
set C3 [db one {SELECT unhex($C3hex)}]
|
||||
|
||||
do_test 6.$tn.3 {
|
||||
list [catch { sqlite3changeset_concat $C1 $C3 } msg] $msg
|
||||
} {1 SQLITE_CORRUPT}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
# 2026 January 09
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix sessionchange2
|
||||
|
||||
do_test 1.1.1 {
|
||||
sqlite3changegroup grp
|
||||
list [catch { grp change_begin INSERT "nosuchtable" 0 } msg] $msg
|
||||
} {1 {SQLITE_ERROR - no such table: nosuchtable}}
|
||||
|
||||
do_test 1.1.2 {
|
||||
grp schema db main
|
||||
list [catch { grp change_begin INSERT "nosuchtable" 0 } msg] $msg
|
||||
} {1 {SQLITE_ERROR - no such table: nosuchtable}}
|
||||
|
||||
do_test 1.1.3 {
|
||||
list [catch { grp change_begin_ne INSERT "nosuchtable" 0 } msg] $msg
|
||||
} {1 SQLITE_ERROR}
|
||||
|
||||
do_execsql_test 1.2.1 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
}
|
||||
do_test 1.2.2 {
|
||||
list [catch { grp change_begin 435 "t1" 0 } msg] $msg
|
||||
} {1 SQLITE_ERROR}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 2.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a, b);
|
||||
}
|
||||
|
||||
do_test 2.1.1 {
|
||||
sqlite3changegroup grp
|
||||
grp schema db main
|
||||
grp change_begin INSERT "t1" 0
|
||||
grp change_int64 new 0 55
|
||||
grp change_int64 new 1 101
|
||||
grp change_finish false
|
||||
} {}
|
||||
|
||||
do_test 2.1.2 {
|
||||
set C [grp output]
|
||||
grp delete
|
||||
changeset_to_list $C
|
||||
} {
|
||||
{INSERT t1 0 X. {} {i 55 i 101}}
|
||||
}
|
||||
|
||||
do_test 2.2.1 {
|
||||
sqlite3changegroup grp
|
||||
grp schema db main
|
||||
grp change_begin INSERT "t2" 0
|
||||
grp change_int64 new 0 -5
|
||||
grp change_int64 new 1 -10
|
||||
grp change_int64 new 2 -20
|
||||
grp change_finish false
|
||||
} {}
|
||||
|
||||
do_test 2.2.2 {
|
||||
set C [grp output]
|
||||
grp delete
|
||||
changeset_to_list $C
|
||||
} {
|
||||
{INSERT t2 0 X.. {} {i -5 i -10 i -20}}
|
||||
}
|
||||
|
||||
do_test 2.2.3 {
|
||||
sqlite3changegroup grp
|
||||
grp schema db main
|
||||
grp change_begin INSERT "t1" 0
|
||||
grp change_int64 new 0 223344
|
||||
grp change_null new 1
|
||||
grp change_finish false
|
||||
} {}
|
||||
|
||||
do_test 2.2.4 {
|
||||
set C [grp output]
|
||||
grp delete
|
||||
changeset_to_list $C
|
||||
} {
|
||||
{INSERT t1 0 X. {} {i 223344 n {}}}
|
||||
}
|
||||
|
||||
do_test 2.2.5 {
|
||||
sqlite3changegroup grp
|
||||
grp schema db main
|
||||
grp change_begin DELETE "t1" 0
|
||||
|
||||
grp change_int64 old 0 1
|
||||
grp change_int64 old 1 123
|
||||
|
||||
grp change_finish false
|
||||
} {}
|
||||
|
||||
do_test 2.2.6 {
|
||||
set C [grp output]
|
||||
grp delete
|
||||
changeset_to_list $C
|
||||
} {
|
||||
{DELETE t1 0 X. {i 1 i 123} {}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b, c);
|
||||
CREATE TABLE t2(x, y);
|
||||
}
|
||||
|
||||
foreach {tn script error} {
|
||||
1 {
|
||||
grp change_begin UPDATE t1 0
|
||||
} {SQLITE_ERROR - invalid change: undefined value in PK of old.* record}
|
||||
|
||||
2 {
|
||||
grp change_begin UPDATE t1 0
|
||||
grp change_int64 old 0 1234
|
||||
grp change_int64 new 0 5678
|
||||
} {SQLITE_ERROR - invalid change: defined value in PK of new.* record}
|
||||
|
||||
3 {
|
||||
grp change_begin UPDATE t1 0
|
||||
grp change_null old 0
|
||||
} {SQLITE_ERROR - invalid change: null value in PK of old.* record}
|
||||
|
||||
4 {
|
||||
grp change_begin UPDATE t1 0
|
||||
grp change_int64 old 0 1234
|
||||
grp change_int64 old 1 20
|
||||
} {SQLITE_ERROR - invalid change: column 1 - old.* value is defined but new.* is undefined}
|
||||
|
||||
5 {
|
||||
grp change_begin UPDATE t1 0
|
||||
grp change_int64 old 0 1234
|
||||
grp change_int64 new 1 20
|
||||
} {SQLITE_ERROR - invalid change: column 1 - old.* value is undefined but new.* is defined}
|
||||
|
||||
6 {
|
||||
grp change_begin INSERT t1 0
|
||||
grp change_null new 0
|
||||
grp change_int64 new 1 20
|
||||
} {SQLITE_ERROR - invalid change: null value in PK}
|
||||
|
||||
7 {
|
||||
grp change_begin INSERT t1 0
|
||||
grp change_int64 new 1 20
|
||||
} {SQLITE_ERROR - invalid change: column 0 is undefined}
|
||||
|
||||
8 {
|
||||
grp change_begin INSERT t1 0
|
||||
grp change_int64 new 0 20
|
||||
} {SQLITE_ERROR - invalid change: column 1 is undefined}
|
||||
|
||||
9 {
|
||||
grp change_begin DELETE t1 0
|
||||
grp change_int64 old 0 20
|
||||
} {SQLITE_ERROR - invalid change: column 1 is undefined}
|
||||
|
||||
10 {
|
||||
grp change_begin DELETE t1 0
|
||||
grp change_int64 old 1 20
|
||||
} {SQLITE_ERROR - invalid change: column 0 is undefined}
|
||||
|
||||
} {
|
||||
sqlite3changegroup grp
|
||||
grp schema db main
|
||||
eval $script
|
||||
do_test 3.1.$tn {
|
||||
list [catch { grp change_finish false } msg] $msg
|
||||
} [list 1 $error]
|
||||
grp delete
|
||||
}
|
||||
|
||||
do_test 3.2.1 {
|
||||
sqlite3changegroup grp
|
||||
grp schema db main
|
||||
grp change_begin DELETE t1 0
|
||||
list [catch {grp change_int64 new 0 20} msg] $msg
|
||||
} {1 SQLITE_ERROR}
|
||||
do_test 3.2.2 {
|
||||
grp change_finish true
|
||||
grp change_begin INSERT t1 0
|
||||
list [catch {grp change_int64 old 0 20} msg] $msg
|
||||
} {1 SQLITE_ERROR}
|
||||
|
||||
grp delete
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a, b);
|
||||
}
|
||||
|
||||
proc do_changegroup_script_test {tn script changeset patchset} {
|
||||
|
||||
breakpoint
|
||||
sqlite3changegroup grp
|
||||
grp schema db main
|
||||
eval $script
|
||||
do_test $tn.1 {
|
||||
changeset_to_list [grp output]
|
||||
} [list {*}$changeset]
|
||||
grp delete
|
||||
|
||||
sqlite3changegroup grp
|
||||
grp config patchset 1
|
||||
grp schema db main
|
||||
eval $script
|
||||
do_test $tn.2 {
|
||||
changeset_to_list [grp output]
|
||||
} [list {*}$patchset]
|
||||
grp delete
|
||||
|
||||
}
|
||||
|
||||
proc do_changegroup_patchset_test {tn script patchset} {
|
||||
|
||||
sqlite3changegroup grp
|
||||
grp config patchset 1
|
||||
grp schema db main
|
||||
eval $script
|
||||
do_test $tn {
|
||||
changeset_to_list [grp output]
|
||||
} [list {*}$patchset]
|
||||
grp delete
|
||||
|
||||
}
|
||||
|
||||
do_changegroup_script_test 3.1 {
|
||||
grp change_begin UPDATE t1 0
|
||||
grp change_int64 old 0 123
|
||||
grp change_int64 old 1 456
|
||||
grp change_int64 new 1 789
|
||||
grp change_finish false
|
||||
|
||||
grp change_begin INSERT t1 0
|
||||
grp change_int64 new 0 124
|
||||
grp change_text new 1 hello_world
|
||||
grp change_finish false
|
||||
|
||||
grp change_begin DELETE t1 0
|
||||
grp change_int64 old 0 125
|
||||
grp change_double old 1 45.67
|
||||
grp change_finish false
|
||||
} {
|
||||
{DELETE t1 0 X. {i 125 f 45.67} {}}
|
||||
{INSERT t1 0 X. {} {i 124 t hello_world}}
|
||||
{UPDATE t1 0 X. {i 123 i 456} {{} {} i 789}}
|
||||
} {
|
||||
{DELETE t1 0 X. {i 125 {} {}} {}}
|
||||
{INSERT t1 0 X. {} {i 124 t hello_world}}
|
||||
{UPDATE t1 0 X. {i 123 {} {}} {{} {} i 789}}
|
||||
}
|
||||
|
||||
do_changegroup_script_test 3.1.2 {
|
||||
grp change_begin INSERT t1 0
|
||||
grp change_int64 new 0 124
|
||||
grp change_text new 1 hello_world
|
||||
grp change_finish false
|
||||
} {
|
||||
{INSERT t1 0 X. {} {i 124 t hello_world}}
|
||||
} {
|
||||
{INSERT t1 0 X. {} {i 124 t hello_world}}
|
||||
}
|
||||
|
||||
do_changegroup_patchset_test 3.2 {
|
||||
grp change_begin UPDATE t1 0
|
||||
grp change_int64 old 0 123
|
||||
grp change_int64 new 1 789
|
||||
grp change_finish false
|
||||
|
||||
grp change_begin DELETE t1 1
|
||||
grp change_int64 old 0 122
|
||||
grp change_finish false
|
||||
} {
|
||||
{DELETE t1 1 X. {i 122 {} {}} {}}
|
||||
{UPDATE t1 0 X. {i 123 {} {}} {{} {} i 789}}
|
||||
}
|
||||
|
||||
do_changegroup_patchset_test 3.3 {
|
||||
grp change_begin DELETE t1 0
|
||||
grp change_int64 old 0 123
|
||||
grp change_finish false
|
||||
} {
|
||||
{DELETE t1 0 X. {i 123 {} {}} {}}
|
||||
}
|
||||
|
||||
do_changegroup_script_test 3.4 {
|
||||
grp change_begin INSERT t1 0
|
||||
grp change_int64 new 0 124
|
||||
grp change_text new 1 hello_world
|
||||
grp change_finish false
|
||||
|
||||
grp change_begin DELETE t1 0
|
||||
grp change_int64 old 0 124
|
||||
grp change_text old 1 hello_world
|
||||
grp change_finish false
|
||||
|
||||
grp change_begin INSERT t1 0
|
||||
grp change_int64 new 0 1000
|
||||
grp change_blob new 1 abcd
|
||||
grp change_finish false
|
||||
|
||||
grp change_begin UPDATE t1 0
|
||||
grp change_int64 old 0 1000
|
||||
grp change_blob old 1 abcd
|
||||
grp change_blob new 1 bcda
|
||||
grp change_finish false
|
||||
} {
|
||||
{INSERT t1 0 X. {} {i 1000 b bcda}}
|
||||
} {
|
||||
{INSERT t1 0 X. {} {i 1000 b bcda}}
|
||||
}
|
||||
|
||||
do_changegroup_script_test 3.5 {
|
||||
grp change_begin DELETE t1 0
|
||||
grp change_int64 old 0 1000
|
||||
grp change_text-1 old 1 "hello world"
|
||||
grp change_finish false
|
||||
} {
|
||||
{DELETE t1 0 X. {i 1000 t {hello world}} {}}
|
||||
} {
|
||||
{DELETE t1 0 X. {i 1000 {} {}} {}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
# Calling sqlite3changegroup_change_finish() if a change has not been
|
||||
# started is a no-op.
|
||||
#
|
||||
do_test 4.0 {
|
||||
sqlite3changegroup grp
|
||||
grp change_finish false
|
||||
} {}
|
||||
|
||||
# But calling any other sqlite3changegroup_change_xxx() function if a
|
||||
# change has not been started is a misuse.
|
||||
#
|
||||
foreach {tn cmd} {
|
||||
1 { grp change_blob old 0 abcd }
|
||||
2 { grp change_text new 1 helloworld }
|
||||
3 { grp change_int64 old 2 12345678 }
|
||||
4 { grp change_double new 3 12.345 }
|
||||
5 { grp change_null old 4 }
|
||||
} {
|
||||
do_test 4.1.$tn {
|
||||
list [catch $::cmd msg] $msg
|
||||
} {1 SQLITE_MISUSE}
|
||||
}
|
||||
grp delete
|
||||
|
||||
do_execsql_test 4.2.1 {
|
||||
CREATE TABLE t3(a, b PRIMARY KEY);
|
||||
}
|
||||
do_test 4.2 {
|
||||
sqlite3changegroup grp
|
||||
grp schema db main
|
||||
grp change_begin INSERT t3 0
|
||||
|
||||
list [catch { grp change_begin INSERT t3 0 } msg] $msg
|
||||
} {1 SQLITE_MISUSE}
|
||||
grp delete
|
||||
|
||||
do_test 4.3 {
|
||||
sqlite3changegroup grp
|
||||
grp schema db main
|
||||
grp change_begin UPDATE t3 0
|
||||
} {}
|
||||
foreach {tn cmd} {
|
||||
1 { grp change_blob old -1 abcd }
|
||||
2 { grp change_text new 2 helloworld }
|
||||
} {
|
||||
do_test 4.4.$tn {
|
||||
list [catch $::cmd msg] $msg
|
||||
} {1 SQLITE_RANGE}
|
||||
}
|
||||
grp delete
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -43,7 +43,7 @@ set C2 [changeset_from_sql {
|
||||
INSERT INTO t1 VALUES(0, 0, 0, 0);
|
||||
}]
|
||||
|
||||
do_faultsim_test 1 -faults oom* -prep {
|
||||
do_faultsim_test 1.2 -faults oom* -prep {
|
||||
sqlite3changegroup G
|
||||
} -body {
|
||||
G schema db main
|
||||
@@ -56,6 +56,20 @@ do_faultsim_test 1 -faults oom* -prep {
|
||||
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
|
||||
}
|
||||
|
||||
do_faultsim_test 1.3 -faults oom* -prep {
|
||||
sqlite3changegroup G
|
||||
} -body {
|
||||
G schema db main
|
||||
G change_begin INSERT t1 0
|
||||
G change_int64 new 0 12345
|
||||
G change_int64 new 1 67890
|
||||
G output
|
||||
set {} {}
|
||||
} -test {
|
||||
catch { G delete }
|
||||
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 2.0 {
|
||||
|
||||
+483
-65
@@ -354,9 +354,10 @@ static int sessionVarintGet(const u8 *aBuf, int *piVal){
|
||||
** Return the number of bytes read.
|
||||
*/
|
||||
static int sessionVarintGetSafe(const u8 *aBuf, int nBuf, int *piVal){
|
||||
u8 aCopy[5];
|
||||
u8 aCopy[9];
|
||||
const u8 *aRead = aBuf;
|
||||
if( nBuf<5 ){
|
||||
memset(aCopy, 0, sizeof(aCopy));
|
||||
if( nBuf<sizeof(aCopy) ){
|
||||
memcpy(aCopy, aBuf, nBuf);
|
||||
aRead = aCopy;
|
||||
}
|
||||
@@ -391,6 +392,19 @@ static void sessionPutI64(u8 *aBuf, sqlite3_int64 i){
|
||||
aBuf[7] = (i>> 0) & 0xFF;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write a double value to the buffer aBuf[].
|
||||
*/
|
||||
static void sessionPutDouble(u8 *aBuf, double r){
|
||||
/* TODO: SQLite does something special to deal with mixed-endian
|
||||
** floating point values (e.g. ARM7). This code probably should
|
||||
** too. */
|
||||
u64 i;
|
||||
assert( sizeof(double)==8 && sizeof(u64)==8 );
|
||||
memcpy(&i, &r, 8);
|
||||
sessionPutI64(aBuf, i);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is used to serialize the contents of value pValue (see
|
||||
** comment titled "RECORD FORMAT" above).
|
||||
@@ -428,16 +442,13 @@ static int sessionSerializeValue(
|
||||
/* TODO: SQLite does something special to deal with mixed-endian
|
||||
** floating point values (e.g. ARM7). This code probably should
|
||||
** too. */
|
||||
u64 i;
|
||||
if( eType==SQLITE_INTEGER ){
|
||||
i = (u64)sqlite3_value_int64(pValue);
|
||||
u64 i = (u64)sqlite3_value_int64(pValue);
|
||||
sessionPutI64(&aBuf[1], i);
|
||||
}else{
|
||||
double r;
|
||||
assert( sizeof(double)==8 && sizeof(u64)==8 );
|
||||
r = sqlite3_value_double(pValue);
|
||||
memcpy(&i, &r, 8);
|
||||
double r = sqlite3_value_double(pValue);
|
||||
sessionPutDouble(&aBuf[1], r);
|
||||
}
|
||||
sessionPutI64(&aBuf[1], i);
|
||||
}
|
||||
nByte = 9;
|
||||
break;
|
||||
@@ -1369,9 +1380,7 @@ static void sessionUpdateOneChange(
|
||||
|
||||
case SQLITE_FLOAT: {
|
||||
double rVal = sqlite3_column_double(pDflt, iField);
|
||||
i64 iVal = 0;
|
||||
memcpy(&iVal, &rVal, sizeof(rVal));
|
||||
sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal);
|
||||
sessionPutDouble(&pNew->aRecord[pNew->nRecord], rVal);
|
||||
pNew->nRecord += 8;
|
||||
break;
|
||||
}
|
||||
@@ -2628,15 +2637,14 @@ static void sessionAppendCol(
|
||||
int eType = sqlite3_column_type(pStmt, iCol);
|
||||
sessionAppendByte(p, (u8)eType, pRc);
|
||||
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
|
||||
sqlite3_int64 i;
|
||||
u8 aBuf[8];
|
||||
if( eType==SQLITE_INTEGER ){
|
||||
i = sqlite3_column_int64(pStmt, iCol);
|
||||
sqlite3_int64 i = sqlite3_column_int64(pStmt, iCol);
|
||||
sessionPutI64(aBuf, i);
|
||||
}else{
|
||||
double r = sqlite3_column_double(pStmt, iCol);
|
||||
memcpy(&i, &r, 8);
|
||||
sessionPutDouble(aBuf, r);
|
||||
}
|
||||
sessionPutI64(aBuf, i);
|
||||
sessionAppendBlob(p, aBuf, 8, pRc);
|
||||
}
|
||||
if( eType==SQLITE_BLOB || eType==SQLITE_TEXT ){
|
||||
@@ -3679,7 +3687,7 @@ static int sessionChangesetBufferRecord(
|
||||
int *pnByte /* OUT: Size of record in bytes */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
int nByte = 0;
|
||||
i64 nByte = 0;
|
||||
int i;
|
||||
for(i=0; rc==SQLITE_OK && i<nCol; i++){
|
||||
int eType;
|
||||
@@ -3688,13 +3696,17 @@ static int sessionChangesetBufferRecord(
|
||||
eType = pIn->aData[pIn->iNext + nByte++];
|
||||
if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
|
||||
int n;
|
||||
nByte += sessionVarintGet(&pIn->aData[pIn->iNext+nByte], &n);
|
||||
int nRem = pIn->nData - (pIn->iNext + nByte);
|
||||
nByte += sessionVarintGetSafe(&pIn->aData[pIn->iNext+nByte], nRem, &n);
|
||||
nByte += n;
|
||||
rc = sessionInputBuffer(pIn, nByte);
|
||||
}else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
|
||||
nByte += 8;
|
||||
}
|
||||
}
|
||||
if( (pIn->iNext+nByte)>pIn->nData ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
}
|
||||
}
|
||||
*pnByte = nByte;
|
||||
return rc;
|
||||
@@ -5677,6 +5689,21 @@ int sqlite3changeset_apply_strm(
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
** The parts of the sqlite3_changegroup structure used by the
|
||||
** sqlite3changegroup_change_xxx() APIs.
|
||||
*/
|
||||
typedef struct ChangeData ChangeData;
|
||||
struct ChangeData {
|
||||
SessionTable *pTab;
|
||||
int bIndirect;
|
||||
int eOp;
|
||||
|
||||
int nBufAlloc;
|
||||
SessionBuffer *aBuf;
|
||||
SessionBuffer record;
|
||||
};
|
||||
|
||||
/*
|
||||
** sqlite3_changegroup handle.
|
||||
*/
|
||||
@@ -5688,12 +5715,17 @@ struct sqlite3_changegroup {
|
||||
|
||||
sqlite3 *db; /* Configured by changegroup_schema() */
|
||||
char *zDb; /* Configured by changegroup_schema() */
|
||||
ChangeData cd; /* Used by changegroup_change_xxx() APIs. */
|
||||
};
|
||||
|
||||
/*
|
||||
** This function is called to merge two changes to the same row together as
|
||||
** part of an sqlite3changeset_concat() operation. A new change object is
|
||||
** allocated and a pointer to it stored in *ppNew.
|
||||
**
|
||||
** Because they have been vetted by sqlite3changegroup_add() or similar,
|
||||
** both the aRec[] change and the pExist change are safe to use without
|
||||
** checking for buffer overflows.
|
||||
*/
|
||||
static int sessionChangeMerge(
|
||||
SessionTable *pTab, /* Table structure */
|
||||
@@ -5834,7 +5866,7 @@ static int sessionChangeMerge(
|
||||
memcpy(aCsr, aRec, nRec);
|
||||
aCsr += nRec;
|
||||
}else{
|
||||
if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aExist, 0,aRec,0) ){
|
||||
if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aExist,0,aRec,0) ){
|
||||
sqlite3_free(pNew);
|
||||
pNew = 0;
|
||||
}
|
||||
@@ -5927,15 +5959,14 @@ static int sessionChangesetExtendRecord(
|
||||
switch( eType ){
|
||||
case SQLITE_FLOAT:
|
||||
case SQLITE_INTEGER: {
|
||||
i64 iVal;
|
||||
if( eType==SQLITE_INTEGER ){
|
||||
iVal = sqlite3_column_int64(pTab->pDfltStmt, ii);
|
||||
}else{
|
||||
double rVal = sqlite3_column_int64(pTab->pDfltStmt, ii);
|
||||
memcpy(&iVal, &rVal, sizeof(i64));
|
||||
}
|
||||
if( SQLITE_OK==sessionBufferGrow(pOut, 8, &rc) ){
|
||||
sessionPutI64(&pOut->aBuf[pOut->nBuf], iVal);
|
||||
if( eType==SQLITE_INTEGER ){
|
||||
sqlite3_int64 iVal = sqlite3_column_int64(pTab->pDfltStmt, ii);
|
||||
sessionPutI64(&pOut->aBuf[pOut->nBuf], iVal);
|
||||
}else{
|
||||
double rVal = sqlite3_column_double(pTab->pDfltStmt, ii);
|
||||
sessionPutDouble(&pOut->aBuf[pOut->nBuf], rVal);
|
||||
}
|
||||
pOut->nBuf += 8;
|
||||
}
|
||||
break;
|
||||
@@ -6006,13 +6037,19 @@ static int sessionChangesetFindTable(
|
||||
int nCol = 0;
|
||||
|
||||
*ppTab = 0;
|
||||
sqlite3changeset_pk(pIter, &abPK, &nCol);
|
||||
|
||||
/* Search the list for an existing table */
|
||||
for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){
|
||||
if( 0==sqlite3_strnicmp(pTab->zName, zTab, nTab+1) ) break;
|
||||
}
|
||||
|
||||
|
||||
if( pIter ){
|
||||
sqlite3changeset_pk(pIter, &abPK, &nCol);
|
||||
}else if( !pTab && !pGrp->db ){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* If one was not found above, create a new table now */
|
||||
if( !pTab ){
|
||||
SessionTable **ppNew;
|
||||
@@ -6024,15 +6061,17 @@ static int sessionChangesetFindTable(
|
||||
memset(pTab, 0, sizeof(SessionTable));
|
||||
pTab->nCol = nCol;
|
||||
pTab->abPK = (u8*)&pTab[1];
|
||||
memcpy(pTab->abPK, abPK, nCol);
|
||||
if( nCol>0 ){
|
||||
memcpy(pTab->abPK, abPK, nCol);
|
||||
}
|
||||
pTab->zName = (char*)&pTab->abPK[nCol];
|
||||
memcpy(pTab->zName, zTab, nTab+1);
|
||||
|
||||
if( pGrp->db ){
|
||||
pTab->nCol = 0;
|
||||
rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb);
|
||||
if( rc ){
|
||||
assert( pTab->azCol==0 );
|
||||
if( rc || pTab->nCol==0 ){
|
||||
sqlite3_free(pTab->azCol);
|
||||
sqlite3_free(pTab);
|
||||
return rc;
|
||||
}
|
||||
@@ -6047,7 +6086,7 @@ static int sessionChangesetFindTable(
|
||||
}
|
||||
|
||||
/* Check that the table is compatible. */
|
||||
if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){
|
||||
if( pIter && !sessionChangesetCheckCompat(pTab, nCol, abPK) ){
|
||||
rc = SQLITE_SCHEMA;
|
||||
}
|
||||
|
||||
@@ -6056,44 +6095,27 @@ static int sessionChangesetFindTable(
|
||||
}
|
||||
|
||||
/*
|
||||
** Add the change currently indicated by iterator pIter to the hash table
|
||||
** belonging to changegroup pGrp.
|
||||
** Add a single change to the changegroup pGrp.
|
||||
*/
|
||||
static int sessionOneChangeToHash(
|
||||
sqlite3_changegroup *pGrp,
|
||||
sqlite3_changeset_iter *pIter,
|
||||
int bRebase
|
||||
sqlite3_changegroup *pGrp, /* Changegroup to update */
|
||||
SessionTable *pTab, /* Table change pertains to */
|
||||
int op, /* One of SQLITE_INSERT, UPDATE, DELETE */
|
||||
int bIndirect, /* True to flag change as "indirect" */
|
||||
int nCol, /* Number of columns in record(s) */
|
||||
u8 *aRec, /* Serialized change record(s) */
|
||||
int nRec, /* Size of aRec[] in bytes */
|
||||
int bRebase /* True if this is a rebase blob */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
int nCol = 0;
|
||||
int op = 0;
|
||||
int iHash = 0;
|
||||
int bIndirect = 0;
|
||||
SessionChange *pChange = 0;
|
||||
SessionChange *pExist = 0;
|
||||
SessionChange **pp = 0;
|
||||
SessionTable *pTab = 0;
|
||||
u8 *aRec = &pIter->in.aData[pIter->in.iCurrent + 2];
|
||||
int nRec = (pIter->in.iNext - pIter->in.iCurrent) - 2;
|
||||
|
||||
assert( nRec>0 );
|
||||
|
||||
/* Ensure that only changesets, or only patchsets, but not a mixture
|
||||
** of both, are being combined. It is an error to try to combine a
|
||||
** changeset and a patchset. */
|
||||
if( pGrp->pList==0 ){
|
||||
pGrp->bPatch = pIter->bPatchset;
|
||||
}else if( pIter->bPatchset!=pGrp->bPatch ){
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
const char *zTab = 0;
|
||||
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
|
||||
rc = sessionChangesetFindTable(pGrp, zTab, pIter, &pTab);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && nCol<pTab->nCol ){
|
||||
if( nCol<pTab->nCol ){
|
||||
SessionBuffer *pBuf = &pGrp->rec;
|
||||
rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, pBuf);
|
||||
aRec = pBuf->aBuf;
|
||||
@@ -6101,7 +6123,7 @@ static int sessionOneChangeToHash(
|
||||
assert( pGrp->db );
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && sessionGrowHash(0, pIter->bPatchset, pTab) ){
|
||||
if( rc==SQLITE_OK && sessionGrowHash(0, pGrp->bPatch, pTab) ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
@@ -6109,12 +6131,12 @@ static int sessionOneChangeToHash(
|
||||
/* Search for existing entry. If found, remove it from the hash table.
|
||||
** Code below may link it back in. */
|
||||
iHash = sessionChangeHash(
|
||||
pTab, (pIter->bPatchset && op==SQLITE_DELETE), aRec, pTab->nChange
|
||||
pTab, (pGrp->bPatch && op==SQLITE_DELETE), aRec, pTab->nChange
|
||||
);
|
||||
for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){
|
||||
int bPkOnly1 = 0;
|
||||
int bPkOnly2 = 0;
|
||||
if( pIter->bPatchset ){
|
||||
if( pGrp->bPatch ){
|
||||
bPkOnly1 = (*pp)->op==SQLITE_DELETE;
|
||||
bPkOnly2 = op==SQLITE_DELETE;
|
||||
}
|
||||
@@ -6129,7 +6151,7 @@ static int sessionOneChangeToHash(
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sessionChangeMerge(pTab, bRebase,
|
||||
pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange
|
||||
pGrp->bPatch, pExist, op, bIndirect, aRec, nRec, &pChange
|
||||
);
|
||||
}
|
||||
if( rc==SQLITE_OK && pChange ){
|
||||
@@ -6138,6 +6160,47 @@ static int sessionOneChangeToHash(
|
||||
pTab->nEntry++;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Add the change currently indicated by iterator pIter to the hash table
|
||||
** belonging to changegroup pGrp.
|
||||
*/
|
||||
static int sessionOneChangeIterToHash(
|
||||
sqlite3_changegroup *pGrp,
|
||||
sqlite3_changeset_iter *pIter,
|
||||
int bRebase
|
||||
){
|
||||
u8 *aRec = &pIter->in.aData[pIter->in.iCurrent + 2];
|
||||
int nRec = (pIter->in.iNext - pIter->in.iCurrent) - 2;
|
||||
const char *zTab = 0;
|
||||
int nCol = 0;
|
||||
int op = 0;
|
||||
int bIndirect = 0;
|
||||
int rc = SQLITE_OK;
|
||||
SessionTable *pTab = 0;
|
||||
|
||||
/* Ensure that only changesets, or only patchsets, but not a mixture
|
||||
** of both, are being combined. It is an error to try to combine a
|
||||
** changeset and a patchset. */
|
||||
if( pGrp->pList==0 ){
|
||||
pGrp->bPatch = pIter->bPatchset;
|
||||
}else if( pIter->bPatchset!=pGrp->bPatch ){
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
|
||||
rc = sessionChangesetFindTable(pGrp, zTab, pIter, &pTab);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sessionOneChangeToHash(
|
||||
pGrp, pTab, op, bIndirect, nCol, aRec, nRec, bRebase
|
||||
);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ) rc = pIter->rc;
|
||||
return rc;
|
||||
}
|
||||
@@ -6157,7 +6220,7 @@ static int sessionChangesetToHash(
|
||||
|
||||
pIter->in.bNoDiscard = 1;
|
||||
while( SQLITE_ROW==(sessionChangesetNext(pIter, &aRec, &nRec, 0)) ){
|
||||
rc = sessionOneChangeToHash(pGrp, pIter, bRebase);
|
||||
rc = sessionOneChangeIterToHash(pGrp, pIter, bRebase);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
}
|
||||
|
||||
@@ -6247,6 +6310,33 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Configure a changegroup object.
|
||||
*/
|
||||
int sqlite3changegroup_config(
|
||||
sqlite3_changegroup *pGrp,
|
||||
int op,
|
||||
void *pArg
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
switch( op ){
|
||||
case SQLITE_CHANGEGROUP_CONFIG_PATCHSET: {
|
||||
int arg = *(int*)pArg;
|
||||
if( pGrp->pList==0 && arg>=0 ){
|
||||
pGrp->bPatch = (arg>0);
|
||||
}
|
||||
*(int*)pArg = pGrp->bPatch;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
rc = SQLITE_MISUSE;
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Provide a database schema to the changegroup object.
|
||||
*/
|
||||
@@ -6305,7 +6395,7 @@ int sqlite3changegroup_add_change(
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
pIter->in.bNoDiscard = 1;
|
||||
rc = sessionOneChangeToHash(pGrp, pIter, 0);
|
||||
rc = sessionOneChangeIterToHash(pGrp, pIter, 0);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -6357,6 +6447,12 @@ int sqlite3changegroup_output_strm(
|
||||
*/
|
||||
void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){
|
||||
if( pGrp ){
|
||||
int ii;
|
||||
for(ii=0; ii<pGrp->cd.nBufAlloc; ii++){
|
||||
sqlite3_free(pGrp->cd.aBuf[ii].aBuf);
|
||||
}
|
||||
sqlite3_free(pGrp->cd.record.aBuf);
|
||||
sqlite3_free(pGrp->cd.aBuf);
|
||||
sqlite3_free(pGrp->zDb);
|
||||
sessionDeleteTable(0, pGrp->pList);
|
||||
sqlite3_free(pGrp->rec.aBuf);
|
||||
@@ -6787,4 +6883,326 @@ int sqlite3session_config(int op, void *pArg){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Begin adding a change to a changegroup object.
|
||||
*/
|
||||
int sqlite3changegroup_change_begin(
|
||||
sqlite3_changegroup *pGrp,
|
||||
int eOp,
|
||||
const char *zTab,
|
||||
int bIndirect,
|
||||
char **pzErr
|
||||
){
|
||||
SessionTable *pTab = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
if( pGrp->cd.pTab ){
|
||||
rc = SQLITE_MISUSE;
|
||||
}else if( eOp!=SQLITE_INSERT && eOp!=SQLITE_UPDATE && eOp!=SQLITE_DELETE ){
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
rc = sessionChangesetFindTable(pGrp, zTab, 0, &pTab);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
if( pTab==0 ){
|
||||
if( pzErr ){
|
||||
*pzErr = sqlite3_mprintf("no such table: %s", zTab);
|
||||
}
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
int nReq = pTab->nCol * (eOp==SQLITE_UPDATE ? 2 : 1);
|
||||
pGrp->cd.pTab = pTab;
|
||||
pGrp->cd.eOp = eOp;
|
||||
pGrp->cd.bIndirect = bIndirect;
|
||||
|
||||
if( pGrp->cd.nBufAlloc<nReq ){
|
||||
SessionBuffer *aBuf = (SessionBuffer*)sqlite3_realloc(
|
||||
pGrp->cd.aBuf, nReq * sizeof(SessionBuffer)
|
||||
);
|
||||
if( aBuf==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
memset(&aBuf[pGrp->cd.nBufAlloc], 0,
|
||||
sizeof(SessionBuffer) * (nReq - pGrp->cd.nBufAlloc)
|
||||
);
|
||||
pGrp->cd.aBuf = aBuf;
|
||||
pGrp->cd.nBufAlloc = nReq;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
{
|
||||
/* Assert that all column values are currently undefined */
|
||||
int ii;
|
||||
for(ii=0; ii<pGrp->cd.nBufAlloc; ii++){
|
||||
assert( pGrp->cd.aBuf[ii].nBuf==0 );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function does processing common to the _change_int64(), _change_text()
|
||||
** and other similar APIs.
|
||||
*/
|
||||
static int checkChangeParams(
|
||||
sqlite3_changegroup *pGrp,
|
||||
int bNew,
|
||||
int iCol,
|
||||
sqlite3_int64 nReq,
|
||||
SessionBuffer **ppBuf
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
if( pGrp->cd.pTab==0 ){
|
||||
rc = SQLITE_MISUSE;
|
||||
}else if( iCol<0 || iCol>=pGrp->cd.pTab->nCol ){
|
||||
rc = SQLITE_RANGE;
|
||||
}else if(
|
||||
(bNew && pGrp->cd.eOp==SQLITE_DELETE)
|
||||
|| (!bNew && pGrp->cd.eOp==SQLITE_INSERT)
|
||||
){
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
SessionBuffer *pBuf = &pGrp->cd.aBuf[iCol];
|
||||
if( pGrp->cd.eOp==SQLITE_UPDATE && bNew ){
|
||||
pBuf += pGrp->cd.pTab->nCol;
|
||||
}
|
||||
pBuf->nBuf = 0;
|
||||
sessionBufferGrow(pBuf, nReq, &rc);
|
||||
pBuf->nBuf = nReq;
|
||||
*ppBuf = pBuf;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Configure the change currently under construction with an integer value.
|
||||
*/
|
||||
int sqlite3changegroup_change_int64(
|
||||
sqlite3_changegroup *pGrp,
|
||||
int bNew,
|
||||
int iCol,
|
||||
sqlite3_int64 iVal
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
SessionBuffer *pBuf = 0;
|
||||
|
||||
if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, 9, &pBuf)) ){
|
||||
return rc;
|
||||
}
|
||||
|
||||
pBuf->aBuf[0] = SQLITE_INTEGER;
|
||||
sessionPutI64(&pBuf->aBuf[1], iVal);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Configure the change currently under construction with a null value.
|
||||
*/
|
||||
int sqlite3changegroup_change_null(
|
||||
sqlite3_changegroup *pGrp,
|
||||
int bNew,
|
||||
int iCol
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
SessionBuffer *pBuf = 0;
|
||||
|
||||
if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, 1, &pBuf)) ){
|
||||
return rc;
|
||||
}
|
||||
|
||||
pBuf->aBuf[0] = SQLITE_NULL;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Configure the change currently under construction with a real value.
|
||||
*/
|
||||
int sqlite3changegroup_change_double(
|
||||
sqlite3_changegroup *pGrp,
|
||||
int bNew,
|
||||
int iCol,
|
||||
double fVal
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
SessionBuffer *pBuf = 0;
|
||||
|
||||
if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, 9, &pBuf)) ){
|
||||
return rc;
|
||||
}
|
||||
|
||||
pBuf->aBuf[0] = SQLITE_FLOAT;
|
||||
sessionPutDouble(&pBuf->aBuf[1], fVal);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Configure the change currently under construction with a text value.
|
||||
*/
|
||||
int sqlite3changegroup_change_text(
|
||||
sqlite3_changegroup *pGrp,
|
||||
int bNew,
|
||||
int iCol,
|
||||
const char *pVal,
|
||||
int nVal
|
||||
){
|
||||
int nText = nVal>=0 ? nVal : strlen(pVal);
|
||||
sqlite3_int64 nByte = 1 + sessionVarintLen(nText) + nText;
|
||||
int rc = SQLITE_OK;
|
||||
SessionBuffer *pBuf = 0;
|
||||
|
||||
if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, nByte, &pBuf)) ){
|
||||
return rc;
|
||||
}
|
||||
|
||||
pBuf->aBuf[0] = SQLITE_TEXT;
|
||||
pBuf->nBuf = (1 + sessionVarintPut(&pBuf->aBuf[1], nText));
|
||||
memcpy(&pBuf->aBuf[pBuf->nBuf], pVal, nText);
|
||||
pBuf->nBuf += nText;
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Configure the change currently under construction with a blob value.
|
||||
*/
|
||||
int sqlite3changegroup_change_blob(
|
||||
sqlite3_changegroup *pGrp,
|
||||
int bNew,
|
||||
int iCol,
|
||||
const void *pVal,
|
||||
int nVal
|
||||
){
|
||||
sqlite3_int64 nByte = 1 + sessionVarintLen(nVal) + nVal;
|
||||
int rc = SQLITE_OK;
|
||||
SessionBuffer *pBuf = 0;
|
||||
|
||||
if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, nByte, &pBuf)) ){
|
||||
return rc;
|
||||
}
|
||||
|
||||
pBuf->aBuf[0] = SQLITE_BLOB;
|
||||
pBuf->nBuf = (1 + sessionVarintPut(&pBuf->aBuf[1], nVal));
|
||||
memcpy(&pBuf->aBuf[pBuf->nBuf], pVal, nVal);
|
||||
pBuf->nBuf += nVal;
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Finish any change currently being constructed by the changegroup object.
|
||||
*/
|
||||
int sqlite3changegroup_change_finish(
|
||||
sqlite3_changegroup *pGrp,
|
||||
int bDiscard,
|
||||
char **pzErr
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
if( pGrp->cd.pTab ){
|
||||
SessionBuffer *aBuf = pGrp->cd.aBuf;
|
||||
int ii;
|
||||
|
||||
if( bDiscard==0 ){
|
||||
int nBuf = pGrp->cd.pTab->nCol;
|
||||
u8 eUndef = SQLITE_NULL;
|
||||
if( pGrp->cd.eOp==SQLITE_UPDATE ){
|
||||
for(ii=0; ii<nBuf; ii++){
|
||||
if( pGrp->cd.pTab->abPK[ii] ){
|
||||
if( aBuf[ii].nBuf<=1 ){
|
||||
*pzErr = sqlite3_mprintf(
|
||||
"invalid change: %s value in PK of old.* record",
|
||||
aBuf[ii].nBuf==1 ? "null" : "undefined"
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
break;
|
||||
}else if( aBuf[ii + nBuf].nBuf>0 ){
|
||||
*pzErr = sqlite3_mprintf(
|
||||
"invalid change: defined value in PK of new.* record"
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
break;
|
||||
}
|
||||
}else
|
||||
if( pGrp->bPatch==0 && (aBuf[ii].nBuf>0)!=(aBuf[ii+nBuf].nBuf>0) ){
|
||||
*pzErr = sqlite3_mprintf(
|
||||
"invalid change: column %d "
|
||||
"- old.* value is %sdefined but new.* is %sdefined",
|
||||
ii, aBuf[ii].nBuf ? "" : "un", aBuf[ii+nBuf].nBuf ? "" : "un"
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
eUndef = 0x00;
|
||||
if( pGrp->bPatch==0 ) nBuf = nBuf * 2;
|
||||
}else{
|
||||
for(ii=0; ii<nBuf; ii++){
|
||||
int isPK = pGrp->cd.pTab->abPK[ii];
|
||||
if( (pGrp->cd.eOp==SQLITE_INSERT || pGrp->bPatch==0 || isPK)
|
||||
&& aBuf[ii].nBuf==0
|
||||
){
|
||||
*pzErr = sqlite3_mprintf(
|
||||
"invalid change: column %d is undefined", ii
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
break;
|
||||
}
|
||||
if( aBuf[ii].nBuf==1 && isPK ){
|
||||
*pzErr = sqlite3_mprintf(
|
||||
"invalid change: null value in PK"
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pGrp->cd.record.nBuf = 0;
|
||||
for(ii=0; ii<nBuf; ii++){
|
||||
SessionBuffer *p = &pGrp->cd.aBuf[ii];
|
||||
if( pGrp->bPatch ){
|
||||
if( pGrp->cd.pTab->abPK[ii]==0 ){
|
||||
if( pGrp->cd.eOp==SQLITE_UPDATE ){
|
||||
p += pGrp->cd.pTab->nCol;
|
||||
}else if( pGrp->cd.eOp==SQLITE_DELETE ){
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( 0==sessionBufferGrow(&pGrp->cd.record, p->nBuf?p->nBuf:1, &rc) ){
|
||||
if( p->nBuf ){
|
||||
memcpy(&pGrp->cd.record.aBuf[pGrp->cd.record.nBuf],p->aBuf,p->nBuf);
|
||||
pGrp->cd.record.nBuf += p->nBuf;
|
||||
}else{
|
||||
pGrp->cd.record.aBuf[pGrp->cd.record.nBuf++] = eUndef;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sessionOneChangeToHash(
|
||||
pGrp, pGrp->cd.pTab,
|
||||
pGrp->cd.eOp, pGrp->cd.bIndirect, pGrp->cd.pTab->nCol,
|
||||
pGrp->cd.record.aBuf, pGrp->cd.record.nBuf, 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset all aBuf[] entries to "undefined". */
|
||||
{
|
||||
int nZero = pGrp->cd.pTab->nCol;
|
||||
if( pGrp->cd.eOp==SQLITE_UPDATE ) nZero += nZero;
|
||||
for(ii=0; ii<nZero; ii++){
|
||||
pGrp->cd.aBuf[ii].nBuf = 0;
|
||||
}
|
||||
}
|
||||
pGrp->cd.pTab = 0;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
|
||||
@@ -1853,6 +1853,232 @@ int sqlite3session_config(int op, void *pArg);
|
||||
*/
|
||||
#define SQLITE_SESSION_CONFIG_STRMSIZE 1
|
||||
|
||||
/*
|
||||
** CAPI3REF: Configure a changegroup object
|
||||
**
|
||||
** Configure the changegroup object passed as the first argument.
|
||||
** At present the only valid value for the second parameter is
|
||||
** [SQLITE_CHANGEGROUP_CONFIG_PATCHSET].
|
||||
*/
|
||||
int sqlite3changegroup_config(sqlite3_changegroup*, int, void *pArg);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Options for sqlite3changegroup_config().
|
||||
**
|
||||
** The following values may be passed as the 2nd parameter to
|
||||
** sqlite3changegroup_config().
|
||||
**
|
||||
** <dt>SQLITE_CHANGEGROUP_CONFIG_PATCHSET <dd>
|
||||
** A changegroup object generates either a changeset or patchset. Usually,
|
||||
** this is determined by whether the first call to sqlite3changegroup_add()
|
||||
** is passed a changeset or a patchset. Or, if the first changes are added
|
||||
** to the changegroup object using the sqlite3changegroup_change_xxx()
|
||||
** APIs, then this option may be used to configure whether the changegroup
|
||||
** object generates a changeset or patchset.
|
||||
**
|
||||
** When this option is invoked, parameter pArg must point to a value of
|
||||
** type int. If the changegroup currently contains zero changes, and the
|
||||
** value of the int variable is zero or greater than zero, then the
|
||||
** changegroup is configured to generate a changeset or patchset,
|
||||
** respectively. It is a no-op, not an error, if the changegroup is not
|
||||
** configured because it has already started accumulating changes.
|
||||
**
|
||||
** Before returning, the int variable is set to 0 if the changegroup is
|
||||
** configured to generate a changeset, or 1 if it is configured to generate
|
||||
** a patchset.
|
||||
*/
|
||||
#define SQLITE_CHANGEGROUP_CONFIG_PATCHSET 1
|
||||
|
||||
|
||||
/*
|
||||
** CAPI3REF: Begin adding a change to a changegroup
|
||||
**
|
||||
** This API is used, in concert with other sqlite3changegroup_change_xxx()
|
||||
** APIs, to add changes to a changegroup object one at a time. To add a
|
||||
** single change, the caller must:
|
||||
**
|
||||
** 1. Invoke sqlite3changegroup_change_begin() to indicate the type of
|
||||
** change (INSERT, UPDATE or DELETE), the affected table and whether
|
||||
** or not the change should be marked as indirect.
|
||||
**
|
||||
** 2. Invoke sqlite3changegroup_change_int64() or one of the other four
|
||||
** value functions - _null(), _double(), _text() or _blob() - one or
|
||||
** more times to specify old.* and new.* values for the change being
|
||||
** constructed.
|
||||
**
|
||||
** 3. Invoke sqlite3changegroup_change_finish() to either finish adding
|
||||
** the change to the group, or to discard the change altogether.
|
||||
**
|
||||
** The first argument to this function must be a pointer to the existing
|
||||
** changegroup object that the change will be added to. The second argument
|
||||
** must be SQLITE_INSERT, SQLITE_UPDATE or SQLITE_DELETE. The third is the
|
||||
** name of the table that the change affects, and the fourth is a boolean
|
||||
** flag specifying whether the change should be marked as "indirect" (if
|
||||
** bIndirect is non-zero) or not indirect (if bIndirect is zero).
|
||||
**
|
||||
** Following a successful call to this function, this function may not be
|
||||
** called again on the same changegroup object until after
|
||||
** sqlite3changegroup_change_finish() has been called. Doing so is an
|
||||
** SQLITE_MISUSE error.
|
||||
**
|
||||
** The changegroup object passed as the first argument must be already
|
||||
** configured with schema data for the specified table. It may be configured
|
||||
** either by calling sqlite3changegroup_schema() with a database that contains
|
||||
** the table, or sqlite3changegroup_add() with a changeset that contains the
|
||||
** table. If the changegroup object has not been configured with a schema for
|
||||
** the specified table when this function is called, SQLITE_ERROR is returned.
|
||||
**
|
||||
** If successful, SQLITE_OK is returned. Otherwise, if an error occurs, an
|
||||
** SQLite error code is returned. In this case, if argument pzErr is non-NULL,
|
||||
** then (*pzErr) may be set to point to a buffer containing a utf-8 formated,
|
||||
** nul-terminated, English language error message. It is the responsibility
|
||||
** of the caller to eventually free this buffer using sqlite3_free().
|
||||
*/
|
||||
int sqlite3changegroup_change_begin(
|
||||
sqlite3_changegroup*,
|
||||
int eOp,
|
||||
const char *zTab,
|
||||
int bIndirect,
|
||||
char **pzErr
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Add a 64-bit integer to a changegroup
|
||||
**
|
||||
** This function may only be called between a successful call to
|
||||
** sqlite3changegroup_change_begin() and its matching
|
||||
** sqlite3changegroup_change_finish() call. If it is called at any
|
||||
** other time, it is an SQLITE_MISUSE error. Calling this function
|
||||
** specifies a 64-bit integer value to be used in the change currently being
|
||||
** added to the changegroup object passed as the first argument.
|
||||
**
|
||||
** The second parameter, bNew, specifies whether the value is to be part of
|
||||
** the new.* (if bNew is non-zero) or old.* (if bNew is zero) record of
|
||||
** the change under construction. If this does not match the type of change
|
||||
** specified by the preceding call to sqlite3changegroup_change_begin() (i.e.
|
||||
** an old.* value for an SQLITE_INSERT change, or a new.* value for an
|
||||
** SQLITE_DELETE), then SQLITE_ERROR is returned.
|
||||
**
|
||||
** The third parameter specifies the column of the old.* or new.* record that
|
||||
** the value will be a part of. If the specified table has an explicit primary
|
||||
** key, then this is the index of the table column, numbered from 0 in the order
|
||||
** specified within the CREATE TABLE statement. Or, if the table uses an
|
||||
** implicit rowid key, then the column 0 is the rowid and the explicit columns
|
||||
** are numbered starting from 1. If the iCol parameter is less than 0 or greater
|
||||
** than the index of the last column in the table, SQLITE_RANGE is returned.
|
||||
**
|
||||
** The fourth parameter is the integer value to use as part of the old.* or
|
||||
** new.* record.
|
||||
**
|
||||
** If this call is successful, SQLITE_OK is returned. Otherwise, if an
|
||||
** error occurs, an SQLite error code is returned.
|
||||
*/
|
||||
int sqlite3changegroup_change_int64(
|
||||
sqlite3_changegroup*,
|
||||
int bNew,
|
||||
int iCol,
|
||||
sqlite3_int64 iVal
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Add a NULL to a changegroup
|
||||
**
|
||||
** This function is similar to sqlite3changegroup_change_int64(). Except that
|
||||
** it configures the change currently under construction with a NULL value
|
||||
** instead of a 64-bit integer.
|
||||
*/
|
||||
int sqlite3changegroup_change_null(sqlite3_changegroup*, int, int);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Add an double to a changegroup
|
||||
**
|
||||
** This function is similar to sqlite3changegroup_change_int64(). Except that
|
||||
** it configures the change currently being constructed with a real value
|
||||
** instead of a 64-bit integer.
|
||||
*/
|
||||
int sqlite3changegroup_change_double(sqlite3_changegroup*, int, int, double);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Add a text value to a changegroup
|
||||
**
|
||||
** This function is similar to sqlite3changegroup_change_int64(). It configures
|
||||
** the currently accumulated change with a text value instead of a 64-bit
|
||||
** integer. Parameter pVal points to a buffer containing the text encoded using
|
||||
** utf-8. Parameter nVal may either be the size of the text value in bytes, or
|
||||
** else a negative value, in which case the buffer pVal points to is assumed to
|
||||
** be nul-terminated.
|
||||
*/
|
||||
int sqlite3changegroup_change_text(
|
||||
sqlite3_changegroup*, int, int, const char *pVal, int nVal
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Add a blob to a changegroup
|
||||
**
|
||||
** This function is similar to sqlite3changegroup_change_int64(). It configures
|
||||
** the currently accumulated change with a blob value instead of a 64-bit
|
||||
** integer. Parameter pVal points to a buffer containing the blob. Parameter
|
||||
** nVal is the size of the blob in bytes.
|
||||
*/
|
||||
int sqlite3changegroup_change_blob(
|
||||
sqlite3_changegroup*, int, int, const void *pVal, int nVal
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Finish adding one-at-at-time changes to a changegroup
|
||||
**
|
||||
** This function may only be called following a successful call to
|
||||
** sqlite3changegroup_change_begin(). Otherwise, it is an SQLITE_MISUSE error.
|
||||
**
|
||||
** If parameter bDiscard is non-zero, then the current change is simply
|
||||
** discarded. In this case this function is always successful and SQLITE_OK
|
||||
** returned.
|
||||
**
|
||||
** If parameter bDiscard is zero, then an attempt is made to add the current
|
||||
** change to the changegroup. Assuming the changegroup is configured to
|
||||
** produce a changeset (not a patchset), this requires that:
|
||||
**
|
||||
** * If the change is an INSERT or DELETE, then a value must be specified
|
||||
** for all columns of the new.* or old.* record, respectively.
|
||||
**
|
||||
** * If the change is an UPDATE record, then values must be provided for
|
||||
** the PRIMARY KEY columns of the old.* record, but must not be provided
|
||||
** for PRIMARY KEY columns of the new.* record.
|
||||
**
|
||||
** * If the change is an UPDATE record, then for each non-PRIMARY KEY
|
||||
** column in the old.* record for which a value has been provided, a
|
||||
** value must also be provided for the same column in the new.* record.
|
||||
** Similarly, for each non-PK column in the old.* record for which
|
||||
** a value is not provided, a value must not be provided for the same
|
||||
** column in the new.* record.
|
||||
**
|
||||
** * All values specified for PRIMARY KEY columns must be non-NULL.
|
||||
**
|
||||
** Otherwise, it is an error.
|
||||
**
|
||||
** If the changegroup already contains a change for the same row (identified
|
||||
** by PRIMARY KEY columns), then the current change is combined with the
|
||||
** existing change in the same way as for sqlite3changegroup_add().
|
||||
**
|
||||
** For a patchset, all of the above rules apply except that it doesn't matter
|
||||
** whether or not values are provided for the non-PK old.* record columns
|
||||
** for an UPDATE or DELETE change. This means that code used to produce
|
||||
** a changeset using the sqlite3changegroup_change_xxx() APIs may also
|
||||
** be used to produce patchsets.
|
||||
**
|
||||
** If the call is successful, SQLITE_OK is returned. Otherwise, if an error
|
||||
** occurs, an SQLite error code is returned. If an error is returned and
|
||||
** parameter pzErr is not NULL, then (*pzErr) may be set to point to a buffer
|
||||
** containing a nul-terminated, utf-8 encoded, English language error message.
|
||||
** It is the responsibility of the caller to eventually free any such error
|
||||
** message buffer using sqlite3_free().
|
||||
*/
|
||||
int sqlite3changegroup_change_finish(
|
||||
sqlite3_changegroup*,
|
||||
int bDiscard,
|
||||
char **pzErr
|
||||
);
|
||||
|
||||
/*
|
||||
** Make sure we can call this stuff from C++.
|
||||
*/
|
||||
|
||||
+236
-8
@@ -13,6 +13,8 @@
|
||||
typedef unsigned char u8;
|
||||
#endif
|
||||
|
||||
extern const char *sqlite3ErrName(int);
|
||||
|
||||
typedef struct TestSession TestSession;
|
||||
struct TestSession {
|
||||
sqlite3_session *pSession;
|
||||
@@ -397,7 +399,6 @@ static int SQLITE_TCLAPI test_session_cmd(
|
||||
}
|
||||
rc = sqlite3session_object_config(pSession, aOpt[iOpt].opt, &iArg);
|
||||
if( rc!=SQLITE_OK ){
|
||||
extern const char *sqlite3ErrName(int);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
}else{
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(iArg));
|
||||
@@ -1114,6 +1115,21 @@ static int SQLITE_TCLAPI test_sqlite3changeset_invert(
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy buffer aIn[] to a new nIn byte buffer obtained from malloc(). Use
|
||||
** plain malloc() instead of any Tcl function because valgrind and asan are
|
||||
** better at detecting small overflows in that case. Avoid sqlite3_malloc()
|
||||
** here because that means dealing with injected OOM errors.
|
||||
**
|
||||
** The caller is responsible for eventually calling free() on the returned
|
||||
** value.
|
||||
*/
|
||||
static u8 *copyToMalloc(const u8 *aIn, int nIn){
|
||||
u8 *pRet = malloc(nIn);
|
||||
memcpy(pRet, aIn, nIn);
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite3changeset_concat LEFT RIGHT
|
||||
*/
|
||||
@@ -1144,6 +1160,9 @@ static int SQLITE_TCLAPI test_sqlite3changeset_concat(
|
||||
sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
|
||||
sRight.nStream = sLeft.nStream;
|
||||
|
||||
sLeft.aData = copyToMalloc(sLeft.aData, sLeft.nData);
|
||||
sRight.aData = copyToMalloc(sRight.aData, sRight.nData);
|
||||
|
||||
if( sLeft.nStream>0 ){
|
||||
rc = sqlite3changeset_concat_strm(
|
||||
testStreamInput, (void*)&sLeft,
|
||||
@@ -1156,6 +1175,9 @@ static int SQLITE_TCLAPI test_sqlite3changeset_concat(
|
||||
);
|
||||
}
|
||||
|
||||
free(sLeft.aData);
|
||||
free(sRight.aData);
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
rc = test_session_error(interp, rc, 0);
|
||||
}else{
|
||||
@@ -1554,6 +1576,14 @@ static void test_changegroup_del(void *clientData){
|
||||
ckfree(pGrp);
|
||||
}
|
||||
|
||||
static int testGetNewOrOld(Tcl_Interp *interp, Tcl_Obj *pObj, int *pbNew){
|
||||
const char *azVal[] = { "old", "new", 0 };
|
||||
int iIdx = 0;
|
||||
int rc = Tcl_GetIndexFromObj(interp, pObj, azVal, "record", 0, &iIdx);
|
||||
*pbNew = iIdx;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: $changegroup schema DB DBNAME
|
||||
** Tclcmd: $changegroup add CHANGESET
|
||||
@@ -1571,14 +1601,25 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
|
||||
const char *zSub;
|
||||
int nArg;
|
||||
const char *zMsg;
|
||||
int iSub;
|
||||
} aSub[] = {
|
||||
{ "schema", 2, "DB DBNAME", }, /* 0 */
|
||||
{ "add", 1, "CHANGESET", }, /* 1 */
|
||||
{ "output", 0, "", }, /* 2 */
|
||||
{ "delete", 0, "", }, /* 3 */
|
||||
{ "add_change", 1, "ITERATOR", }, /* 4 */
|
||||
{ 0 }
|
||||
{ "schema", 2, "DB DBNAME" }, /* 0 */
|
||||
{ "add", 1, "CHANGESET" }, /* 1 */
|
||||
{ "output", 0, "" }, /* 2 */
|
||||
{ "delete", 0, "" }, /* 3 */
|
||||
{ "add_change", 1, "ITERATOR" }, /* 4 */
|
||||
|
||||
{ "change_begin", 3, "TYPE TABLE INDIRECT" }, /* 5 */
|
||||
{ "change_int64", 3, "[new|old] ICOL VALUE" }, /* 6 */
|
||||
{ "change_null", 2, "[new|old] ICOL" }, /* 7 */
|
||||
{ "change_double", 3, "[new|old] ICOL VALUE" }, /* 8 */
|
||||
{ "change_text", 3, "[new|old] ICOL VALUE" }, /* 9 */
|
||||
{ "change_blob", 3, "[new|old] ICOL VALUE" }, /* 10 */
|
||||
{ "change_finish", 1, "BDISCARD" }, /* 11 */
|
||||
|
||||
{ "config", 2, "OPTION INTVAL" }, /* 12 */
|
||||
{ "change_text-1", 3, "[new|old] ICOL VALUE" }, /* 13 */
|
||||
{ "change_begin_ne", 3, "TYPE TABLE INDIRECT" }, /* 14 */
|
||||
{ 0, 0, 0 }
|
||||
};
|
||||
int rc = TCL_OK;
|
||||
int iSub = 0;
|
||||
@@ -1647,6 +1688,193 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
|
||||
break;
|
||||
};
|
||||
|
||||
case 14: /* change_beginne */
|
||||
case 5: { /* change_begin */
|
||||
struct ChangeType {
|
||||
const char *zType;
|
||||
int eType;
|
||||
} aType[] = {
|
||||
{ "INSERT", SQLITE_INSERT },
|
||||
{ "UPDATE", SQLITE_UPDATE },
|
||||
{ "DELETE", SQLITE_DELETE },
|
||||
{ 0, 0 }
|
||||
};
|
||||
int eType = 0;
|
||||
const char *zTab = 0;
|
||||
int bIndirect;
|
||||
int iIdx = 0;
|
||||
char *zErr = 0;
|
||||
char **pz = ((iSub==5) ? &zErr : 0);
|
||||
|
||||
if( TCL_OK!=Tcl_GetIntFromObj(0, objv[2], &eType) ){
|
||||
rc = Tcl_GetIndexFromObjStruct(
|
||||
interp, objv[2], aType, sizeof(aType[0]), "TYPE", 0, &iIdx
|
||||
);
|
||||
if( rc!=TCL_OK ) return rc;
|
||||
eType = aType[iIdx].eType;
|
||||
}
|
||||
zTab = Tcl_GetString(objv[3]);
|
||||
if( Tcl_GetBooleanFromObj(interp, objv[4], &bIndirect) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
rc = sqlite3changegroup_change_begin(p->pGrp, eType, zTab, bIndirect, pz);
|
||||
assert( zErr==0 || rc!=SQLITE_OK );
|
||||
if( rc!=SQLITE_OK ){
|
||||
rc = test_session_error(interp, rc, zErr);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 6: { /* change_int64 */
|
||||
int bNew = 0;
|
||||
int iCol = 0;
|
||||
sqlite3_int64 iVal = 0;
|
||||
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|
||||
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
|
||||
|| TCL_OK!=Tcl_GetWideIntFromObj(interp, objv[4], &iVal)
|
||||
){
|
||||
rc = TCL_ERROR;
|
||||
}else{
|
||||
rc = sqlite3changegroup_change_int64(p->pGrp, bNew, iCol, iVal);
|
||||
if( rc!=SQLITE_OK ){
|
||||
rc = test_session_error(interp, rc, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 7: { /* change_null */
|
||||
int bNew = 0;
|
||||
int iCol = 0;
|
||||
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|
||||
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
|
||||
){
|
||||
rc = TCL_ERROR;
|
||||
}else{
|
||||
rc = sqlite3changegroup_change_null(p->pGrp, bNew, iCol);
|
||||
if( rc!=SQLITE_OK ){
|
||||
rc = test_session_error(interp, rc, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 8: { /* change_double */
|
||||
int bNew = 0;
|
||||
int iCol = 0;
|
||||
double rVal = 0;
|
||||
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|
||||
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
|
||||
|| TCL_OK!=Tcl_GetDoubleFromObj(interp, objv[4], &rVal)
|
||||
){
|
||||
rc = TCL_ERROR;
|
||||
}else{
|
||||
rc = sqlite3changegroup_change_double(p->pGrp, bNew, iCol, rVal);
|
||||
if( rc!=SQLITE_OK ){
|
||||
rc = test_session_error(interp, rc, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 9: { /* change_text */
|
||||
int bNew = 0;
|
||||
int iCol = 0;
|
||||
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|
||||
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
|
||||
){
|
||||
rc = TCL_ERROR;
|
||||
}else{
|
||||
Tcl_Size nVal = 0;
|
||||
const char *pVal = Tcl_GetStringFromObj(objv[4], &nVal);
|
||||
rc = sqlite3changegroup_change_text(p->pGrp, bNew, iCol, pVal, nVal);
|
||||
if( rc!=SQLITE_OK ){
|
||||
rc = test_session_error(interp, rc, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 10: { /* change_blob */
|
||||
int bNew = 0;
|
||||
int iCol = 0;
|
||||
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|
||||
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
|
||||
){
|
||||
rc = TCL_ERROR;
|
||||
}else{
|
||||
Tcl_Size nVal = 0;
|
||||
const u8 *pVal = Tcl_GetByteArrayFromObj(objv[4], &nVal);
|
||||
rc = sqlite3changegroup_change_blob(p->pGrp, bNew, iCol, pVal, nVal);
|
||||
if( rc!=SQLITE_OK ){
|
||||
rc = test_session_error(interp, rc, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 11: { /* change_finish */
|
||||
int bDiscard = 0;
|
||||
if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[2], &bDiscard) ){
|
||||
rc = TCL_ERROR;
|
||||
}else{
|
||||
char *zErr = 0;
|
||||
rc = sqlite3changegroup_change_finish(p->pGrp, bDiscard, &zErr);
|
||||
if( rc!=SQLITE_OK ){
|
||||
rc = test_session_error(interp, rc, zErr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 12: { /* config */
|
||||
struct OptionName {
|
||||
const char *zOpt;
|
||||
int op;
|
||||
} aOp[] = {
|
||||
{ "patchset", SQLITE_CHANGEGROUP_CONFIG_PATCHSET },
|
||||
{ 0, 0 }
|
||||
};
|
||||
int iIdx = 0;
|
||||
int iArg = 0;
|
||||
rc = Tcl_GetIndexFromObjStruct(
|
||||
interp, objv[2], aOp, sizeof(aOp[0]), "option", 0, &iIdx
|
||||
);
|
||||
if( rc==TCL_OK
|
||||
&& (rc = Tcl_GetIntFromObj(interp, objv[3], &iArg))==TCL_OK
|
||||
){
|
||||
int op = aOp[iIdx].op;
|
||||
void *pArg = (void*)&iArg;
|
||||
|
||||
rc = sqlite3changegroup_config(p->pGrp, op, pArg);
|
||||
if( rc!=SQLITE_OK ){
|
||||
rc = test_session_error(interp, rc, 0);
|
||||
}else{
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(iArg));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 13: { /* change_text-1 */
|
||||
int bNew = 0;
|
||||
int iCol = 0;
|
||||
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|
||||
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
|
||||
){
|
||||
rc = TCL_ERROR;
|
||||
}else{
|
||||
const char *pVal = Tcl_GetString(objv[4]);
|
||||
rc = sqlite3changegroup_change_text(p->pGrp, bNew, iCol, pVal, -1);
|
||||
if( rc!=SQLITE_OK ){
|
||||
rc = test_session_error(interp, rc, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: { /* delete */
|
||||
assert( iSub==3 );
|
||||
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
|
||||
|
||||
+14
-10
@@ -206,7 +206,7 @@ b.mkdir@ = if [ ! -d $(dir $@) ]; then \
|
||||
# $1 = logtag, $2 = src file(s). $3 = dest dir
|
||||
b.cp = $(call b.mkdir@); \
|
||||
echo '$(logtag.$(1)) $(emo.disk) $(2) ==> $3'; \
|
||||
cp -p $(2) $(3) || exit
|
||||
cp -f -p $(2) $(3) || exit
|
||||
|
||||
#
|
||||
# $(call b.c-pp.shcmd,LOGTAG,src,dest,-Dx=y...)
|
||||
@@ -566,12 +566,14 @@ WASM_CUSTOM_INSTANTIATE = 1
|
||||
# -D... flags which should be included in all invocations should be
|
||||
# appended to $(b.c-pp.target.flags).
|
||||
#
|
||||
bin.c-pp = ./c-pp-lite
|
||||
$(bin.c-pp): c-pp-lite.c $(sqlite3.c) $(MAKEFILE)
|
||||
$(CC) -O0 -o $@ c-pp-lite.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \
|
||||
bin.c-pp = ./c-pp
|
||||
$(bin.c-pp): libcmpp.c $(sqlite3.c) $(MAKEFILE)
|
||||
$(CC) -O0 -o $@ libcmpp.c $(dir.top)/sqlite3.c -I$(dir.top) \
|
||||
-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 \
|
||||
-DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_WAL -DSQLITE_THREADSAFE=0 \
|
||||
-DSQLITE_TEMP_STORE=3
|
||||
-DSQLITE_TEMP_STORE=3 \
|
||||
'-DCMPP_DEFAULT_DELIM="//#"' -DCMPP_MAIN -DCMPP_OMIT_D_MODULE \
|
||||
-DCMPP_OMIT_D_PIPE
|
||||
DISTCLEAN_FILES += $(bin.c-pp)
|
||||
b.c-pp.target.flags ?=
|
||||
ifeq (1,$(SQLITE_C_IS_SEE))
|
||||
@@ -911,8 +913,10 @@ ifeq (0,$(wasm-bare-bones))
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-vtab-helper.c-pp.js
|
||||
endif
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-kvvfs.c-pp.js
|
||||
sqlite3-api.jses += $(dir.api)/opfs-common-shared.c-pp.js
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-sahpool.c-pp.js
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-wl.c-pp.js
|
||||
|
||||
# Parallel builds can fail if $(sqlite3-license-version.js) is not
|
||||
# created early enough, so make all files in $(sqlite-api.jses) except
|
||||
@@ -1182,13 +1186,13 @@ all: demos
|
||||
#######################################################################
|
||||
|
||||
#
|
||||
# "SOAP" is a static file which is not part of the amalgamation but
|
||||
# gets copied into the build output folder and into each of the fiddle
|
||||
# builds.
|
||||
# "SOAP" is not part of the amalgamation but gets copied into the
|
||||
# build output folder and into each of the fiddle builds.
|
||||
#
|
||||
sqlite3.ext.js += $(dir.dout)/sqlite3-opfs-async-proxy.js
|
||||
$(dir.dout)/sqlite3-opfs-async-proxy.js: $(dir.api)/sqlite3-opfs-async-proxy.js
|
||||
@$(call b.cp,@,$<,$@)
|
||||
$(eval $(call b.c-pp.target,soap,\
|
||||
$(dir.api)/sqlite3-opfs-async-proxy.c-pp.js,\
|
||||
$(dir.dout)/sqlite3-opfs-async-proxy.js))
|
||||
|
||||
#
|
||||
# Add a dep of $(sqlite3.ext.js) on every individual build's JS file.
|
||||
|
||||
@@ -13,6 +13,7 @@ _sqlite3_bind_parameter_index
|
||||
_sqlite3_bind_parameter_name
|
||||
_sqlite3_bind_pointer
|
||||
_sqlite3_bind_text
|
||||
_sqlite3_bind_zeroblob
|
||||
_sqlite3_busy_handler
|
||||
_sqlite3_busy_timeout
|
||||
_sqlite3_cancel_auto_extension
|
||||
@@ -225,14 +226,14 @@ _sqlite3session_object_config
|
||||
_sqlite3session_patchset
|
||||
_sqlite3session_patchset_strm
|
||||
_sqlite3session_table_filter
|
||||
//#endif not bare-bones
|
||||
//#/if not bare-bones
|
||||
//#if enable-see
|
||||
_sqlite3_key
|
||||
_sqlite3_key_v2
|
||||
_sqlite3_rekey
|
||||
_sqlite3_rekey_v2
|
||||
_sqlite3_activate_see
|
||||
//#endif enable-see
|
||||
//#/if enable-see
|
||||
//#if fiddle
|
||||
_fiddle_db_arg
|
||||
_fiddle_db_filename
|
||||
@@ -244,4 +245,5 @@ _fiddle_reset_db
|
||||
_fiddle_db_handle
|
||||
_fiddle_db_vfs
|
||||
_fiddle_export_db
|
||||
//#endif fiddle
|
||||
_fiddle_get_prompt
|
||||
//#/if fiddle
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
//#if target:es6-module
|
||||
const toExportForESM =
|
||||
//#endif
|
||||
//#/if
|
||||
(function(){
|
||||
//console.warn("this is extern-post-js");
|
||||
/**
|
||||
@@ -97,7 +97,7 @@ const toExportForESM =
|
||||
//console.warn("sqlite3InitModule() returning E-module.",EmscriptenModule);
|
||||
return EmscriptenModule;
|
||||
}
|
||||
//#endif
|
||||
//#/if
|
||||
return s;
|
||||
}).catch((e)=>{
|
||||
console.error("Exception loading sqlite3 module:",e);
|
||||
@@ -128,10 +128,10 @@ const toExportForESM =
|
||||
}
|
||||
/* AMD modules get injected in a way we cannot override,
|
||||
so we can't handle those here. */
|
||||
//#endif // !target:es6-module
|
||||
//#/if // !target:es6-module
|
||||
return sIM;
|
||||
})();
|
||||
//#if target:es6-module
|
||||
sqlite3InitModule = toExportForESM;
|
||||
export default sqlite3InitModule;
|
||||
//#endif
|
||||
//#/if
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
//#if 0
|
||||
/**
|
||||
This file is for preprocessor #include into the "opfs" and
|
||||
"opfs-wl" impls, as well as their async-proxy part. It must be
|
||||
inlined in those files, as opposed to being a shared copy in the
|
||||
library, because (A) the async proxy does not load the library and
|
||||
(B) it references an object which is local to each of those files
|
||||
but which has a 99% identical structure for each.
|
||||
*/
|
||||
//#/if
|
||||
//#// vfs.metrics.enable is a refactoring crutch.
|
||||
//#define vfs.metrics.enable 0
|
||||
const initS11n = function(){
|
||||
/**
|
||||
This proxy de/serializes cross-thread function arguments and
|
||||
output-pointer values via the state.sabIO SharedArrayBuffer,
|
||||
using the region defined by (state.sabS11nOffset,
|
||||
state.sabS11nOffset + state.sabS11nSize]. Only one dataset is
|
||||
recorded at a time.
|
||||
|
||||
This is not a general-purpose format. It only supports the
|
||||
range of operations, and data sizes, needed by the
|
||||
sqlite3_vfs and sqlite3_io_methods operations. Serialized
|
||||
data are transient and this serialization algorithm may
|
||||
change at any time.
|
||||
|
||||
The data format can be succinctly summarized as:
|
||||
|
||||
Nt...Td...D
|
||||
|
||||
Where:
|
||||
|
||||
- N = number of entries (1 byte)
|
||||
|
||||
- t = type ID of first argument (1 byte)
|
||||
|
||||
- ...T = type IDs of the 2nd and subsequent arguments (1 byte
|
||||
each).
|
||||
|
||||
- d = raw bytes of first argument (per-type size).
|
||||
|
||||
- ...D = raw bytes of the 2nd and subsequent arguments (per-type
|
||||
size).
|
||||
|
||||
All types except strings have fixed sizes. Strings are stored
|
||||
using their TextEncoder/TextDecoder representations. It would
|
||||
arguably make more sense to store them as Int16Arrays of
|
||||
their JS character values, but how best/fastest to get that
|
||||
in and out of string form is an open point. Initial
|
||||
experimentation with that approach did not gain us any speed.
|
||||
|
||||
Historical note: this impl was initially about 1% this size by
|
||||
using using JSON.stringify/parse(), but using fit-to-purpose
|
||||
serialization saves considerable runtime.
|
||||
*/
|
||||
if(state.s11n) return state.s11n;
|
||||
const textDecoder = new TextDecoder(),
|
||||
textEncoder = new TextEncoder('utf-8'),
|
||||
viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
|
||||
viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
|
||||
state.s11n = Object.create(null);
|
||||
/* Only arguments and return values of these types may be
|
||||
serialized. This covers the whole range of types needed by the
|
||||
sqlite3_vfs API. */
|
||||
const TypeIds = Object.create(null);
|
||||
TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
|
||||
TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
|
||||
TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
|
||||
TypeIds.string = { id: 4 };
|
||||
|
||||
const getTypeId = (v)=>(
|
||||
TypeIds[typeof v]
|
||||
|| toss("Maintenance required: this value type cannot be serialized.",v)
|
||||
);
|
||||
const getTypeIdById = (tid)=>{
|
||||
switch(tid){
|
||||
case TypeIds.number.id: return TypeIds.number;
|
||||
case TypeIds.bigint.id: return TypeIds.bigint;
|
||||
case TypeIds.boolean.id: return TypeIds.boolean;
|
||||
case TypeIds.string.id: return TypeIds.string;
|
||||
default: toss("Invalid type ID:",tid);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Returns an array of the deserialized state stored by the most
|
||||
recent serialize() operation (from this thread or the
|
||||
counterpart thread), or null if the serialization buffer is
|
||||
empty. If passed a truthy argument, the serialization buffer
|
||||
is cleared after deserialization.
|
||||
*/
|
||||
state.s11n.deserialize = function(clear=false){
|
||||
//#if vfs.metrics.enable
|
||||
++metrics.s11n.deserialize.count;
|
||||
//#/if
|
||||
const t = performance.now();
|
||||
const argc = viewU8[0];
|
||||
const rc = argc ? [] : null;
|
||||
if(argc){
|
||||
const typeIds = [];
|
||||
let offset = 1, i, n, v;
|
||||
for(i = 0; i < argc; ++i, ++offset){
|
||||
typeIds.push(getTypeIdById(viewU8[offset]));
|
||||
}
|
||||
for(i = 0; i < argc; ++i){
|
||||
const t = typeIds[i];
|
||||
if(t.getter){
|
||||
v = viewDV[t.getter](offset, state.littleEndian);
|
||||
offset += t.size;
|
||||
}else{/*String*/
|
||||
n = viewDV.getInt32(offset, state.littleEndian);
|
||||
offset += 4;
|
||||
v = textDecoder.decode(viewU8.slice(offset, offset+n));
|
||||
offset += n;
|
||||
}
|
||||
rc.push(v);
|
||||
}
|
||||
}
|
||||
if(clear) viewU8[0] = 0;
|
||||
//log("deserialize:",argc, rc);
|
||||
//#if vfs.metrics.enable
|
||||
metrics.s11n.deserialize.time += performance.now() - t;
|
||||
//#/if
|
||||
return rc;
|
||||
};
|
||||
|
||||
/**
|
||||
Serializes all arguments to the shared buffer for consumption
|
||||
by the counterpart thread.
|
||||
|
||||
This routine is only intended for serializing OPFS VFS
|
||||
arguments and (in at least one special case) result values,
|
||||
and the buffer is sized to be able to comfortably handle
|
||||
those.
|
||||
|
||||
If passed no arguments then it zeroes out the serialization
|
||||
state.
|
||||
*/
|
||||
state.s11n.serialize = function(...args){
|
||||
const t = performance.now();
|
||||
//#if vfs.metrics.enable
|
||||
++metrics.s11n.serialize.count;
|
||||
//#/if
|
||||
if(args.length){
|
||||
//log("serialize():",args);
|
||||
const typeIds = [];
|
||||
let i = 0, offset = 1;
|
||||
viewU8[0] = args.length & 0xff /* header = # of args */;
|
||||
for(; i < args.length; ++i, ++offset){
|
||||
/* Write the TypeIds.id value into the next args.length
|
||||
bytes. */
|
||||
typeIds.push(getTypeId(args[i]));
|
||||
viewU8[offset] = typeIds[i].id;
|
||||
}
|
||||
for(i = 0; i < args.length; ++i) {
|
||||
/* Deserialize the following bytes based on their
|
||||
corresponding TypeIds.id from the header. */
|
||||
const t = typeIds[i];
|
||||
if(t.setter){
|
||||
viewDV[t.setter](offset, args[i], state.littleEndian);
|
||||
offset += t.size;
|
||||
}else{/*String*/
|
||||
const s = textEncoder.encode(args[i]);
|
||||
viewDV.setInt32(offset, s.byteLength, state.littleEndian);
|
||||
offset += 4;
|
||||
viewU8.set(s, offset);
|
||||
offset += s.byteLength;
|
||||
}
|
||||
}
|
||||
//log("serialize() result:",viewU8.slice(0,offset));
|
||||
}else{
|
||||
viewU8[0] = 0;
|
||||
}
|
||||
//#if vfs.metrics.enable
|
||||
metrics.s11n.serialize.time += performance.now() - t;
|
||||
//#/if
|
||||
};
|
||||
|
||||
//#if defined opfs-async-proxy
|
||||
state.s11n.storeException = state.asyncS11nExceptions
|
||||
? ((priority,e)=>{
|
||||
if(priority<=state.asyncS11nExceptions){
|
||||
state.s11n.serialize([e.name,': ',e.message].join(""));
|
||||
}
|
||||
})
|
||||
: ()=>{};
|
||||
//#/if
|
||||
|
||||
return state.s11n;
|
||||
//#undef vfs.metrics.enable
|
||||
}/*initS11n()*/;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@ Module.runSQLite3PostLoadInit = async function(
|
||||
- sqlite3-vtab-helper.c-pp.js => Utilities for virtual table impls
|
||||
- sqlite3-vfs-opfs.c-pp.js => OPFS VFS
|
||||
- sqlite3-vfs-opfs-sahpool.c-pp.js => OPFS SAHPool VFS
|
||||
- sqlite3-vfs-opfs-wl.c-pp.js => WebLock-using OPFS VFS
|
||||
- post-js-footer.js => this file's epilogue
|
||||
|
||||
And all of that gets sandwiched between extern-pre-js.js and
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
/**
|
||||
This file was preprocessed using:
|
||||
|
||||
//#@policy error
|
||||
//#@ policy error
|
||||
@c-pp::argv@
|
||||
//#@policy off
|
||||
//#@ policy off
|
||||
*/
|
||||
//#if unsupported-build
|
||||
/**
|
||||
@@ -30,9 +30,10 @@
|
||||
load. It may not work properly. Only builds _directly_ targeting
|
||||
browser environments ("vanilla" JS and ESM modules) are supported
|
||||
and tested. Builds which _indirectly_ target browsers (namely
|
||||
bundler-friendly builds) are not supported deliverables.
|
||||
bundler-friendly builds and any node builds) are not supported
|
||||
deliverables.
|
||||
*/
|
||||
//#endif
|
||||
//#/if
|
||||
//#if not target:es6-bundler-friendly
|
||||
(function(Module){
|
||||
const sIMS =
|
||||
@@ -101,7 +102,7 @@
|
||||
"result =", theFile
|
||||
);
|
||||
return theFile;
|
||||
//#endif target:es6-module
|
||||
//#/if target:es6-module
|
||||
}.bind(sIMS);
|
||||
|
||||
//#if Module.instantiateWasm and not wasmfs and not target:node
|
||||
@@ -147,7 +148,7 @@
|
||||
.then(finalThen)
|
||||
return loadWasm();
|
||||
}.bind(sIMS);
|
||||
//#endif Module.instantiateWasm and not wasmfs
|
||||
//#/if Module.instantiateWasm and not wasmfs
|
||||
})(Module);
|
||||
//#endif not target:es6-bundler-friendly
|
||||
//#/if not target:es6-bundler-friendly
|
||||
/* END FILE: api/pre-js.js. */
|
||||
|
||||
@@ -98,6 +98,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
["sqlite3_bind_parameter_name", "string", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_bind_pointer", "int",
|
||||
"sqlite3_stmt*", "int", "*", "string:static", "*"],
|
||||
/* sqlite_bind_text() is hand-written */
|
||||
["sqlite3_bind_zeroblob", "int", "sqlite3_stmt*", "int", "int"],
|
||||
["sqlite3_busy_handler","int", [
|
||||
"sqlite3*",
|
||||
new wasm.xWrap.FuncPtrAdapter({
|
||||
@@ -120,11 +122,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
["sqlite3_column_double","f64", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_column_int","int", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_column_name","string", "sqlite3_stmt*", "int"],
|
||||
//#define proxy-text-apis=1
|
||||
//#define proxy-text-apis 1
|
||||
//#if not proxy-text-apis
|
||||
/* Search this file for tag:proxy-text-apis to see what this is about. */
|
||||
["sqlite3_column_text","string", "sqlite3_stmt*", "int"],
|
||||
//#endif
|
||||
//#/if
|
||||
["sqlite3_column_type","int", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_column_value","sqlite3_value*", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_commit_hook", "void*", [
|
||||
@@ -324,7 +326,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
["sqlite3_value_subtype", "int", "sqlite3_value*"],
|
||||
//#if not proxy-text-apis
|
||||
["sqlite3_value_text", "string", "sqlite3_value*"],
|
||||
//#endif
|
||||
//#/if
|
||||
["sqlite3_value_type", "int", "sqlite3_value*"],
|
||||
["sqlite3_vfs_find", "*", "string"],
|
||||
["sqlite3_vfs_register", "int", "sqlite3_vfs*", "int"],
|
||||
@@ -500,7 +502,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
["sqlite3_activate_see", undefined, "string"]
|
||||
);
|
||||
}
|
||||
//#endif enable-see
|
||||
//#/if enable-see
|
||||
|
||||
if( wasm.bigIntEnabled && !!wasm.exports.sqlite3_declare_vtab ){
|
||||
bindingSignatures.int64.push(
|
||||
@@ -1694,7 +1696,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
: null;
|
||||
};
|
||||
}/*text-return-related bindings*/
|
||||
//#endif proxy-text-apis
|
||||
//#/if proxy-text-apis
|
||||
|
||||
{/* sqlite3_config() */
|
||||
/**
|
||||
|
||||
@@ -215,7 +215,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
if(stmt) stmt.finalize();
|
||||
}
|
||||
};
|
||||
//#endif enable-see
|
||||
//#/if enable-see
|
||||
|
||||
/**
|
||||
A proxy for DB class constructors. It must be called with the
|
||||
@@ -301,7 +301,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
try{
|
||||
//#if enable-see
|
||||
dbCtorApplySEEKey(this,opt);
|
||||
//#endif
|
||||
//#/if
|
||||
// Check for per-VFS post-open SQL/callback...
|
||||
const pVfs = capi.sqlite3_js_db_vfs(pDb)
|
||||
|| toss3("Internal error: cannot get VFS for new db handle.");
|
||||
@@ -453,7 +453,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
is supplied and the database is encrypted, execution of the
|
||||
post-initialization SQL will fail, causing the constructor to
|
||||
throw.
|
||||
//#endif enable-see
|
||||
//#/if enable-see
|
||||
|
||||
The `filename` and `vfs` arguments may be either JS strings or
|
||||
C-strings allocated via WASM. `flags` is required to be a JS
|
||||
@@ -1141,7 +1141,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
return arg.returnVal();
|
||||
}/*exec()*/,
|
||||
|
||||
//#if nope
|
||||
//#if 0
|
||||
/**
|
||||
Experimental and untested - do not use.
|
||||
|
||||
@@ -1265,7 +1265,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
}
|
||||
return this;
|
||||
}/*forEachStmt()*/,
|
||||
//#endif nope
|
||||
//#/if nope
|
||||
|
||||
/**
|
||||
Creates a new UDF (User-Defined Function) which is accessible
|
||||
@@ -1559,18 +1559,18 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
only argument. On success, returns the result of the
|
||||
callback. Throws on error.
|
||||
|
||||
Note that transactions may not be nested, so this will throw if
|
||||
it is called recursively. For nested transactions, use the
|
||||
Transactions may not be nested, so this will throw if it is
|
||||
called recursively. For nested transactions, use the
|
||||
savepoint() method or manually manage SAVEPOINTs using exec().
|
||||
|
||||
If called with 2 arguments, the first must be a keyword which
|
||||
is legal immediately after a BEGIN statement, e.g. one of
|
||||
"DEFERRED", "IMMEDIATE", or "EXCLUSIVE". Though the exact list
|
||||
"DEFERRED", "IMMEDIATE", or "EXCLUSIVE", though the exact list
|
||||
of supported keywords is not hard-coded here, in order to be
|
||||
future-compatible, if the argument does not look like a single
|
||||
keyword then an exception is triggered with a description of
|
||||
the problem.
|
||||
*/
|
||||
*/
|
||||
transaction: function(/* [beginQualifier,] */callback){
|
||||
let opener = 'BEGIN';
|
||||
if(arguments.length>1){
|
||||
@@ -2426,4 +2426,4 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
});
|
||||
//#else
|
||||
/* Built with the omit-oo1 flag. */
|
||||
//#endif if not omit-oo1
|
||||
//#/if if not omit-oo1
|
||||
|
||||
@@ -99,6 +99,13 @@
|
||||
used in WASMFS-capable builds of the library (which the canonical
|
||||
builds do not include).
|
||||
|
||||
- `disable` (as of 3.53.0) may be an object with the following
|
||||
properties:
|
||||
- `vfs`, an object, may contain a map of VFS names to booleans.
|
||||
Any mapping to falsy are disabled. The supported names
|
||||
are: "kvvfs", "opfs", "opfs-sahpool", "opfs-wl".
|
||||
- Other disabling options may be added in the future.
|
||||
|
||||
[^1] = This property may optionally be a function, in which case
|
||||
this function calls that function to fetch the value,
|
||||
enabling delayed evaluation.
|
||||
@@ -145,7 +152,8 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
);
|
||||
return sqlite3ApiBootstrap.sqlite3;
|
||||
}
|
||||
const config = Object.assign(Object.create(null),{
|
||||
const nu = (...obj)=>Object.assign(Object.create(null),...obj);
|
||||
const config = nu({
|
||||
exports: undefined,
|
||||
memory: undefined,
|
||||
bigIntEnabled: !!globalThis.BigInt64Array,
|
||||
@@ -162,7 +170,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
certain wasm.xWrap.resultAdapter()s.
|
||||
*/
|
||||
useStdAlloc: false
|
||||
}, apiConfig || {});
|
||||
}, apiConfig);
|
||||
|
||||
Object.assign(config, {
|
||||
allocExportName: config.useStdAlloc ? 'malloc' : 'sqlite3_malloc',
|
||||
@@ -195,7 +203,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
not documented are installed as 1-to-1 proxies for their
|
||||
C-side counterparts.
|
||||
*/
|
||||
const capi = Object.create(null);
|
||||
const capi = nu();
|
||||
/**
|
||||
Holds state which are specific to the WASM-related
|
||||
infrastructure and glue code.
|
||||
@@ -204,7 +212,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
dynamically after the api object is fully constructed, so
|
||||
not all are documented in this file.
|
||||
*/
|
||||
const wasm = Object.create(null);
|
||||
const wasm = nu();
|
||||
|
||||
/** Internal helper for SQLite3Error ctor. */
|
||||
const __rcStr = (rc)=>{
|
||||
@@ -752,6 +760,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
toss: function(...args){throw new Error(args.join(' '))},
|
||||
toss3,
|
||||
typedArrayPart: wasm.typedArrayPart,
|
||||
nu,
|
||||
assert: function(arg,msg){
|
||||
if( !arg ){
|
||||
util.toss("Assertion failed:",msg);
|
||||
@@ -1008,7 +1017,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true;
|
||||
};
|
||||
}
|
||||
const rc = Object.create(null), ov = [0,0];
|
||||
const rc = nu(), ov = [0,0];
|
||||
let i = 0, k;
|
||||
while((k = capi.sqlite3_compileoption_get(i++))){
|
||||
f._opt(k,ov);
|
||||
@@ -1016,7 +1025,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
}
|
||||
return f._result = rc;
|
||||
}else if(Array.isArray(optName)){
|
||||
const rc = Object.create(null);
|
||||
const rc = nu();
|
||||
optName.forEach((v)=>{
|
||||
rc[v] = capi.sqlite3_compileoption_used(v);
|
||||
});
|
||||
@@ -1067,7 +1076,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
The memory lives in the WASM heap and can be used with routines
|
||||
such as wasm.poke() and wasm.heap8u().slice().
|
||||
*/
|
||||
wasm.pstack = Object.assign(Object.create(null),{
|
||||
wasm.pstack = nu({
|
||||
/**
|
||||
Sets the current pstack position to the given pointer. Results
|
||||
are undefined if the passed-in value did not come from
|
||||
@@ -1289,7 +1298,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
// sqlite3__wasm_init_wasmfs() is not available
|
||||
return this.dir = "";
|
||||
}
|
||||
}.bind(Object.create(null));
|
||||
}.bind(nu());
|
||||
|
||||
/**
|
||||
Returns true if sqlite3.capi.sqlite3_wasmfs_opfs_dir() is a
|
||||
@@ -1643,6 +1652,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
case capi.SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE:
|
||||
case capi.SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE:
|
||||
case capi.SQLITE_DBCONFIG_ENABLE_COMMENTS:
|
||||
case capi.SQLITE_DBCONFIG_FP_DIGITS:
|
||||
if( !this.ip ){
|
||||
this.ip = wasm.xWrap('sqlite3__wasm_db_config_ip','int',
|
||||
['sqlite3*', 'int', 'int', '*']);
|
||||
@@ -1664,7 +1674,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
default:
|
||||
return capi.SQLITE_MISUSE;
|
||||
}
|
||||
}.bind(Object.create(null));
|
||||
}.bind(nu());
|
||||
|
||||
/**
|
||||
Given a (sqlite3_value*), this function attempts to convert it
|
||||
@@ -1898,7 +1908,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
if(rc) return SQLite3Error.toss(rc,arguments[2]+"() failed with code "+rc);
|
||||
const pv = wasm.peekPtr(this.ptr);
|
||||
return pv ? capi.sqlite3_value_to_js( pv, true ) : undefined;
|
||||
}.bind(Object.create(null));
|
||||
}.bind(nu());
|
||||
|
||||
/**
|
||||
A wrapper around sqlite3_preupdate_new() which fetches the
|
||||
@@ -1938,6 +1948,63 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
'sqlite3changeset_old');
|
||||
}/*changeset/preupdate additions*/
|
||||
|
||||
/**
|
||||
sqlite3_js_retry_busy(maxTimes,callback[,beforeRetry])
|
||||
|
||||
Calls the given _synchronous_ callback function. If that function
|
||||
returns sqlite3.capi.SQLITE_BUSY _or_ throws an SQLite3Error
|
||||
with a resultCode property of that value then it will suppress
|
||||
that error and try again, up to the given maximum number of
|
||||
times. If the callback returns any other value than that,
|
||||
it is returned. If the maximum number of retries has been
|
||||
reached, an SQLite3Error with a resultCode value of
|
||||
sqlite3.capi.SQLITE_BUSY is thrown. If the callback throws any
|
||||
exception other than the aforementioned BUSY exception, it is
|
||||
propagated. If it throws a BUSY exception on its final attempt,
|
||||
that is propagated as well.
|
||||
|
||||
If the beforeRetry argument is given, it must be a _synchronous_
|
||||
function. It is called immediately before each retry of the
|
||||
callback (not for the initial call), passed the attempt number
|
||||
(so it starts with 2, not 1). If it throws, the exception is
|
||||
handled as described above. Its result value is ignored.
|
||||
|
||||
To effectively retry "forever", pass a huge maxTimes value such
|
||||
as Number.MAX_SAFE_INTEGER, with the caveat that there is no
|
||||
recovery from that unless the beforeRetry() can figure out when
|
||||
to throw.
|
||||
|
||||
Added in 3.53.0.
|
||||
|
||||
TODO?: an async variant of this.
|
||||
*/
|
||||
capi.sqlite3_js_retry_busy = function(maxTimes, callback, beforeRetry){
|
||||
for(let n = 1; n <= maxTimes; ++n){
|
||||
try{
|
||||
if( beforeRetry && n>1 ) beforeRetry(n);
|
||||
const rc = callback();
|
||||
if( capi.SQLITE_BUSY===rc ){
|
||||
if( n===maxTimes ){
|
||||
throw new SQLite3Error(rc, [
|
||||
"sqlite3_js_retry_busy() max retry attempts (",
|
||||
maxTimes,
|
||||
") reached."
|
||||
].join(''));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return rc;
|
||||
}catch(e){
|
||||
if( n<maxTimes
|
||||
&& (e instanceof SQLite3Error)
|
||||
&& e.resultCode===capi.SQLITE_BUSY ){
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* The remainder of the API will be set up in later steps. */
|
||||
const sqlite3 = {
|
||||
WasmAllocError: WasmAllocError,
|
||||
@@ -1956,7 +2023,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
This object is initially a placeholder which gets replaced by a
|
||||
build-generated object.
|
||||
*/
|
||||
version: Object.create(null),
|
||||
version: nu(),
|
||||
|
||||
/**
|
||||
The library reserves the 'client' property for client-side use
|
||||
@@ -2002,6 +2069,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
so that we can add tests for them. */
|
||||
delete sqlite3.util;
|
||||
delete sqlite3.StructBinder;
|
||||
delete sqlite3.opfs;
|
||||
}
|
||||
return sqlite3;
|
||||
};
|
||||
@@ -2021,20 +2089,22 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
return ff.isReady = p.catch(catcher);
|
||||
}.bind(sqlite3ApiBootstrap),
|
||||
/**
|
||||
scriptInfo ideally gets injected into this object by the
|
||||
infrastructure which assembles the JS/WASM module. It contains
|
||||
state which must be collected before sqlite3ApiBootstrap() can
|
||||
be declared. It is not necessarily available to any
|
||||
sqlite3ApiBootstrap.initializers but "should" be in place (if
|
||||
it's added at all) by the time that
|
||||
sqlite3ApiBootstrap.initializersAsync is processed.
|
||||
scriptInfo holds information about the currenty-loading script
|
||||
so that we can locate the WASM file if it's somewhere other
|
||||
than the build-time-defined directory. It ideally gets injected
|
||||
into this object by the infrastructure which assembles the
|
||||
JS/WASM module. It contains state which must be collected
|
||||
before sqlite3ApiBootstrap() can be declared. It is not
|
||||
necessarily available to any sqlite3ApiBootstrap.initializers
|
||||
but "should" be in place (if it's added at all) by the time
|
||||
that sqlite3ApiBootstrap.initializersAsync is processed.
|
||||
|
||||
This state is not part of the public API, only intended for use
|
||||
with the sqlite3 API bootstrapping and wasm-loading process.
|
||||
*/
|
||||
scriptInfo: undefined
|
||||
};
|
||||
if( ('undefined'!==typeof sqlite3IsUnderTest/* from post-js-header.js */) ){
|
||||
if( 'undefined'!==typeof sqlite3IsUnderTest/* from post-js-header.js */ ){
|
||||
sqlite3.__isUnderTest = !!sqlite3IsUnderTest;
|
||||
}
|
||||
try{
|
||||
@@ -2071,10 +2141,10 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
loader, and if we lose that capability for some reason then
|
||||
we'll lose access to this metadata.
|
||||
|
||||
These data are interesting for exploring how the wasm/JS
|
||||
pieces connect, e.g. for exploring exactly what Emscripten
|
||||
imports into WASM from its JS glue, but it's not
|
||||
SQLite-related.
|
||||
These data are interesting for exploring how the wasm/JS pieces
|
||||
connect, e.g. for exploring exactly what Emscripten imports into
|
||||
WASM from its JS glue, but it's not SQLite-related and is not
|
||||
required for the library to work.
|
||||
*/
|
||||
const iw = sqlite3.scriptInfo?.instantiateWasm;
|
||||
if( iw ){
|
||||
|
||||
@@ -677,4 +677,4 @@ sqlite3.initWorker1API = function(){
|
||||
});
|
||||
//#else
|
||||
/* Built with the omit-oo1 flag. */
|
||||
//#endif if not omit-oo1
|
||||
//#/if if not omit-oo1
|
||||
|
||||
+364
-191
@@ -46,10 +46,52 @@
|
||||
theFunc().then(...) is not compatible with the change to
|
||||
synchronous, but we do do not use those APIs that way. i.e. we don't
|
||||
_need_ to change anything for this, but at some point (after Chrome
|
||||
versions (approximately) 104-107 are extinct) should change our
|
||||
versions (approximately) 104-107 are extinct) we should change our
|
||||
usage of those methods to remove the "await".
|
||||
*/
|
||||
//#if 0
|
||||
/**
|
||||
2026-04-04: this file gets included by both the "opfs" and "opfs-wl"
|
||||
VFSes. It would, in hindsight, hypothetically be possible to restructure
|
||||
it very slightly to support both VFSes via a single Worker instance.
|
||||
|
||||
Some of the changes we would need for that:
|
||||
|
||||
- The xLock/xUnlock "op codes" would need to differ for each impl.
|
||||
i.e. we'd need state.opIds.xLock{,WL} and state.opIds.xUnlock{,WL}
|
||||
to distinguish between the two, rather than doing so when this Worker
|
||||
is loaded.
|
||||
|
||||
- We would need to centralize loading of this Worker, outside of
|
||||
the VFS-specific pieces, and change the handshake in order to be
|
||||
able to distinguish between clients which support
|
||||
Atomics.waitAsync() and those which do not ("opfs-wl" requires
|
||||
waitAsync()).
|
||||
|
||||
One down-side would be for clients which, for whatever reason, want
|
||||
to use both "opfs" and "opfs-wl" within the same session: because
|
||||
both would go through the same Worker, any operations for one VFS
|
||||
would, while they're being processed on this side of the proxy,
|
||||
effectively block the other VFS from doing anything, potentially
|
||||
deadlocking. This use case seems unlikely enough that it can
|
||||
possibly be ruled out (or even reasonably flat-out prohibited by
|
||||
the library).
|
||||
*/
|
||||
//#/if
|
||||
|
||||
"use strict";
|
||||
const urlParams = new URL(globalThis.location.href).searchParams;
|
||||
const vfsName = urlParams.get('vfs');
|
||||
if( !vfsName ){
|
||||
throw new Error("Expecting vfs=opfs|opfs-wl URL argument for this worker");
|
||||
}
|
||||
/**
|
||||
We use this to allow us to differentiate debug output from
|
||||
multiple instances, e.g. multiple Workers to the "opfs"
|
||||
VFS or both the "opfs" and "opfs-wl" VFSes.
|
||||
*/
|
||||
const workerId = (Math.random() * 10000000) | 0;
|
||||
const isWebLocker = 'opfs-wl'===urlParams.get('vfs');
|
||||
const wPost = (type,...args)=>postMessage({type, payload:args});
|
||||
const installAsyncProxy = function(){
|
||||
const toss = function(...args){throw new Error(args.join(' '))};
|
||||
@@ -66,6 +108,13 @@ const installAsyncProxy = function(){
|
||||
*/
|
||||
const state = Object.create(null);
|
||||
|
||||
/* initS11n() is preprocessor-injected so that we have identical
|
||||
copies in the synchronous and async halves. This side does not
|
||||
load the SQLite library, so does not have access to that copy. */
|
||||
//#define opfs-async-proxy
|
||||
//#include api/opfs-common-inline.c-pp.js
|
||||
//#undef opfs-async-proxy
|
||||
|
||||
/**
|
||||
verbose:
|
||||
|
||||
@@ -82,7 +131,7 @@ const installAsyncProxy = function(){
|
||||
2:console.log.bind(console)
|
||||
};
|
||||
const logImpl = (level,...args)=>{
|
||||
if(state.verbose>level) loggers[level]("OPFS asyncer:",...args);
|
||||
if(state.verbose>level) loggers[level](vfsName+' async-proxy',workerId+":",...args);
|
||||
};
|
||||
const log = (...args)=>logImpl(2, ...args);
|
||||
const warn = (...args)=>logImpl(1, ...args);
|
||||
@@ -97,12 +146,13 @@ const installAsyncProxy = function(){
|
||||
*/
|
||||
const __openFiles = Object.create(null);
|
||||
/**
|
||||
__implicitLocks is a Set of sqlite3_file pointers (integers) which were
|
||||
"auto-locked". i.e. those for which we obtained a sync access
|
||||
handle without an explicit xLock() call. Such locks will be
|
||||
released during db connection idle time, whereas a sync access
|
||||
handle obtained via xLock(), or subsequently xLock()'d after
|
||||
auto-acquisition, will not be released until xUnlock() is called.
|
||||
__implicitLocks is a Set of sqlite3_file pointers (integers)
|
||||
which were "auto-locked". i.e. those for which we necessarily
|
||||
obtain a sync access handle without an explicit xLock() call
|
||||
guarding access. Such locks will be released during
|
||||
`waitLoop()`'s idle time, whereas a sync access handle obtained
|
||||
via xLock(), or subsequently xLock()'d after auto-acquisition,
|
||||
will not be released until xUnlock() is called.
|
||||
|
||||
Maintenance reminder: if we relinquish auto-locks at the end of the
|
||||
operation which acquires them, we pay a massive performance
|
||||
@@ -271,10 +321,11 @@ const installAsyncProxy = function(){
|
||||
|
||||
In order to help alleviate cross-tab contention for a dabase, if
|
||||
an exception is thrown while acquiring the handle, this routine
|
||||
will wait briefly and try again, up to some fixed number of
|
||||
times. If acquisition still fails at that point it will give up
|
||||
and propagate the exception. Client-level code will see that as
|
||||
an I/O error.
|
||||
will wait briefly and try again, up to `maxTries` of times. If
|
||||
acquisition still fails at that point it will give up and
|
||||
propagate the exception. Client-level code will see that either
|
||||
as an I/O error or SQLITE_BUSY, depending on the exception and
|
||||
the context.
|
||||
|
||||
2024-06-12: there is a rare race condition here which has been
|
||||
reported a single time:
|
||||
@@ -289,13 +340,31 @@ const installAsyncProxy = function(){
|
||||
there's another race condition there). That's easy to say but
|
||||
creating a viable test for that condition has proven challenging
|
||||
so far.
|
||||
|
||||
Interface quirk: if fh.xLock is falsy and the handle is acquired
|
||||
then fh.fid is added to __implicitLocks(). If fh.xLock is truthy,
|
||||
it is not added as an implicit lock. i.e. xLock() impls must set
|
||||
fh.xLock immediately _before_ calling this and must arrange to
|
||||
restore it to its previous value if this function throws.
|
||||
|
||||
2026-03-06:
|
||||
|
||||
- baseWaitTime is the number of milliseconds to wait for the
|
||||
first retry, increasing by one factor for each retry. It defaults
|
||||
to (state.asyncIdleWaitTime*2).
|
||||
|
||||
- maxTries is the number of attempt to make, each one spaced out
|
||||
by one additional factor of the baseWaitTime (e.g. 300, then 600,
|
||||
then 900, the 1200...). This MUST be an integer >0.
|
||||
|
||||
Only the Web Locks impl should use the 3rd and 4th parameters.
|
||||
*/
|
||||
const getSyncHandle = async (fh,opName)=>{
|
||||
const getSyncHandle = async (fh, opName, baseWaitTime, maxTries = 6)=>{
|
||||
if(!fh.syncHandle){
|
||||
const t = performance.now();
|
||||
log("Acquiring sync handle for",fh.filenameAbs);
|
||||
const maxTries = 6,
|
||||
msBase = state.asyncIdleWaitTime * 2;
|
||||
const msBase = baseWaitTime ?? (state.asyncIdleWaitTime * 2);
|
||||
maxTries ??= 6;
|
||||
let i = 1, ms = msBase;
|
||||
for(; true; ms = msBase * ++i){
|
||||
try {
|
||||
@@ -329,6 +398,9 @@ const installAsyncProxy = function(){
|
||||
/**
|
||||
Stores the given value at state.sabOPView[state.opIds.rc] and then
|
||||
Atomics.notify()'s it.
|
||||
|
||||
The opName is only used for logging and debugging - all result
|
||||
codes are expected on the same state.sabOPView slot.
|
||||
*/
|
||||
const storeAndNotify = (opName, value)=>{
|
||||
log(opName+"() => notify(",value,")");
|
||||
@@ -458,24 +530,12 @@ const installAsyncProxy = function(){
|
||||
await releaseImplicitLock(fh);
|
||||
storeAndNotify('xFileSize', rc);
|
||||
},
|
||||
xLock: async function(fid/*sqlite3_file pointer*/,
|
||||
lockType/*SQLITE_LOCK_...*/){
|
||||
const fh = __openFiles[fid];
|
||||
let rc = 0;
|
||||
const oldLockType = fh.xLock;
|
||||
fh.xLock = lockType;
|
||||
if( !fh.syncHandle ){
|
||||
try {
|
||||
await getSyncHandle(fh,'xLock');
|
||||
__implicitLocks.delete(fid);
|
||||
}catch(e){
|
||||
state.s11n.storeException(1,e);
|
||||
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_LOCK);
|
||||
fh.xLock = oldLockType;
|
||||
}
|
||||
}
|
||||
storeAndNotify('xLock',rc);
|
||||
},
|
||||
/**
|
||||
The first argument is semantically invalid here - it's an
|
||||
address in the synchronous side's heap. We can do nothing with
|
||||
it here except use it as a unique-per-file identifier.
|
||||
i.e. a lookup key.
|
||||
*/
|
||||
xOpen: async function(fid/*sqlite3_file pointer*/, filename,
|
||||
flags/*SQLITE_OPEN_...*/,
|
||||
opfsFlags/*OPFS_...*/){
|
||||
@@ -533,7 +593,7 @@ const installAsyncProxy = function(){
|
||||
rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ;
|
||||
}
|
||||
}catch(e){
|
||||
error("xRead() failed",e,fh);
|
||||
//error("xRead() failed",e,fh);
|
||||
state.s11n.storeException(1,e);
|
||||
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_READ);
|
||||
}
|
||||
@@ -560,15 +620,192 @@ const installAsyncProxy = function(){
|
||||
affirmNotRO('xTruncate', fh);
|
||||
await (await getSyncHandle(fh,'xTruncate')).truncate(size);
|
||||
}catch(e){
|
||||
error("xTruncate():",e,fh);
|
||||
//error("xTruncate():",e,fh);
|
||||
state.s11n.storeException(2,e);
|
||||
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_TRUNCATE);
|
||||
}
|
||||
await releaseImplicitLock(fh);
|
||||
storeAndNotify('xTruncate',rc);
|
||||
},
|
||||
xUnlock: async function(fid/*sqlite3_file pointer*/,
|
||||
lockType/*SQLITE_LOCK_...*/){
|
||||
xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){
|
||||
let rc;
|
||||
const fh = __openFiles[fid];
|
||||
try{
|
||||
affirmNotRO('xWrite', fh);
|
||||
rc = (
|
||||
n === (await getSyncHandle(fh,'xWrite'))
|
||||
.write(fh.sabView.subarray(0, n),
|
||||
{at: Number(offset64)})
|
||||
) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE;
|
||||
}catch(e){
|
||||
//error("xWrite():",e,fh);
|
||||
state.s11n.storeException(1,e);
|
||||
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_WRITE);
|
||||
}
|
||||
await releaseImplicitLock(fh);
|
||||
storeAndNotify('xWrite',rc);
|
||||
}
|
||||
}/*vfsAsyncImpls*/;
|
||||
|
||||
if( isWebLocker ){
|
||||
/* We require separate xLock() and xUnlock() implementations for the
|
||||
original and Web Lock implementations. The ones in this block
|
||||
are for the WebLock impl.
|
||||
|
||||
The Golden Rule for this impl is: if we have a web lock, we
|
||||
must also hold the SAH. When "upgrading" an implicit lock to a
|
||||
requested (explicit) lock, we must remove the SAH from the
|
||||
__implicitLocks set. When we unlock, we release both the web
|
||||
lock and the SAH. That invariant must be kept intact or race
|
||||
conditions on SAHs will ensue.
|
||||
*/
|
||||
/** Registry of active Web Locks: fid -> { mode, resolveRelease } */
|
||||
const __activeWebLocks = Object.create(null);
|
||||
|
||||
vfsAsyncImpls.xLock = async function(fid/*sqlite3_file pointer*/,
|
||||
lockType/*SQLITE_LOCK_...*/,
|
||||
isFromUnlock/*only if called from this.xUnlock()*/){
|
||||
const whichOp = isFromUnlock ? 'xUnlock' : 'xLock';
|
||||
const fh = __openFiles[fid];
|
||||
//error("xLock()",fid, lockType, isFromUnlock, fh);
|
||||
const requestedMode = (lockType >= state.sq3Codes.SQLITE_LOCK_RESERVED)
|
||||
? 'exclusive' : 'shared';
|
||||
const existing = __activeWebLocks[fid];
|
||||
if( existing ){
|
||||
if( existing.mode === requestedMode
|
||||
|| (existing.mode === 'exclusive'
|
||||
&& requestedMode === 'shared') ) {
|
||||
fh.xLock = lockType;
|
||||
storeAndNotify(whichOp, 0);
|
||||
/* Don't do this: existing.mode = requestedMode;
|
||||
|
||||
Paraphrased from advice given by a consulting developer:
|
||||
|
||||
If you hold an exclusive lock and SQLite requests shared,
|
||||
you should keep exiting.mode as exclusive in because the
|
||||
underlying Web Lock is still exclusive. Changing it to
|
||||
shared would trick xLock into thinking it needs to
|
||||
perform a release/re-acquire dance if an exclusive is
|
||||
later requested.
|
||||
*/
|
||||
return 0 /* Already held at required or higher level */;
|
||||
}
|
||||
/*
|
||||
Upgrade path: we must release shared and acquire exclusive.
|
||||
This transition is NOT atomic in Web Locks API.
|
||||
|
||||
It _effectively_ is atomic if we don't call
|
||||
closeSyncHandle(fh), as no other worker can lock that until
|
||||
we let it go. But we can't do that without eventually
|
||||
leading to deadly embrace situations, so we don't do that.
|
||||
(That's not a hypothetical, it has happened.)
|
||||
*/
|
||||
await closeSyncHandle(fh);
|
||||
existing.resolveRelease();
|
||||
delete __activeWebLocks[fid];
|
||||
}
|
||||
|
||||
const lockName = "sqlite3-vfs-opfs:" + fh.filenameAbs;
|
||||
const oldLockType = fh.xLock;
|
||||
return new Promise((resolveWaitLoop) => {
|
||||
//log("xLock() initial promise entered...");
|
||||
navigator.locks.request(lockName, { mode: requestedMode }, async (lock) => {
|
||||
//log("xLock() Web Lock entered.", fh);
|
||||
__implicitLocks.delete(fid);
|
||||
let rc = 0;
|
||||
try{
|
||||
fh.xLock = lockType/*must be set before getSyncHandle() is called!*/;
|
||||
await getSyncHandle(fh, 'xLock', state.asyncIdleWaitTime, 5);
|
||||
}catch(e){
|
||||
fh.xLock = oldLockType;
|
||||
state.s11n.storeException(1, e);
|
||||
rc = GetSyncHandleError.convertRc(e, state.sq3Codes.SQLITE_BUSY);
|
||||
}
|
||||
const releasePromise = rc
|
||||
? undefined
|
||||
: new Promise((resolveRelease) => {
|
||||
__activeWebLocks[fid] = { mode: requestedMode, resolveRelease };
|
||||
});
|
||||
storeAndNotify(whichOp, rc) /* unblock the C side */;
|
||||
resolveWaitLoop(0) /* unblock waitLoop() */;
|
||||
await releasePromise /* hold the lock until xUnlock */;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/** Internal helper for the opfs-wl xUnlock() */
|
||||
const wlCloseHandle = async(fh)=>{
|
||||
let rc = 0;
|
||||
try{
|
||||
/* For the record, we've never once seen closeSyncHandle()
|
||||
throw, nor should it because destructors do not throw. */
|
||||
await closeSyncHandle(fh);
|
||||
}catch(e){
|
||||
state.s11n.storeException(1,e);
|
||||
rc = state.sq3Codes.SQLITE_IOERR_UNLOCK;
|
||||
}
|
||||
return rc;
|
||||
};
|
||||
|
||||
vfsAsyncImpls.xUnlock = async function(fid/*sqlite3_file pointer*/,
|
||||
lockType/*SQLITE_LOCK_...*/){
|
||||
const fh = __openFiles[fid];
|
||||
const existing = __activeWebLocks[fid];
|
||||
if( !existing ){
|
||||
const rc = await wlCloseHandle(fh);
|
||||
storeAndNotify('xUnlock', rc);
|
||||
return rc;
|
||||
}
|
||||
//log("xUnlock()",fid, lockType, fh);
|
||||
let rc = 0;
|
||||
if( lockType === state.sq3Codes.SQLITE_LOCK_NONE ){
|
||||
/* SQLite usually unlocks all the way to NONE */
|
||||
rc = await wlCloseHandle(fh);
|
||||
existing.resolveRelease();
|
||||
delete __activeWebLocks[fid];
|
||||
fh.xLock = lockType;
|
||||
}else if( lockType === state.sq3Codes.SQLITE_LOCK_SHARED
|
||||
&& existing.mode === 'exclusive' ){
|
||||
/* downgrade EXCLUSIVE -> SHARED */
|
||||
rc = await wlCloseHandle(fh);
|
||||
if( 0===rc ){
|
||||
fh.xLock = lockType;
|
||||
existing.resolveRelease();
|
||||
delete __activeWebLocks[fid];
|
||||
return vfsAsyncImpls.xLock(fid, lockType, true);
|
||||
}
|
||||
}else{
|
||||
/* ??? */
|
||||
error("xUnlock() unhandled condition", fh);
|
||||
}
|
||||
storeAndNotify('xUnlock', rc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}else{
|
||||
/* Original/"legacy" xLock() and xUnlock() */
|
||||
|
||||
vfsAsyncImpls.xLock = async function(fid/*sqlite3_file pointer*/,
|
||||
lockType/*SQLITE_LOCK_...*/){
|
||||
const fh = __openFiles[fid];
|
||||
let rc = 0;
|
||||
const oldLockType = fh.xLock;
|
||||
fh.xLock = lockType;
|
||||
if( !fh.syncHandle ){
|
||||
try {
|
||||
await getSyncHandle(fh,'xLock');
|
||||
__implicitLocks.delete(fid);
|
||||
}catch(e){
|
||||
state.s11n.storeException(1,e);
|
||||
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_LOCK);
|
||||
fh.xLock = oldLockType;
|
||||
}
|
||||
}
|
||||
storeAndNotify('xLock',rc);
|
||||
};
|
||||
|
||||
vfsAsyncImpls.xUnlock = async function(fid/*sqlite3_file pointer*/,
|
||||
lockType/*SQLITE_LOCK_...*/){
|
||||
let rc = 0;
|
||||
const fh = __openFiles[fid];
|
||||
if( fh.syncHandle
|
||||
@@ -582,173 +819,102 @@ const installAsyncProxy = function(){
|
||||
}
|
||||
}
|
||||
storeAndNotify('xUnlock',rc);
|
||||
},
|
||||
xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){
|
||||
let rc;
|
||||
const fh = __openFiles[fid];
|
||||
try{
|
||||
affirmNotRO('xWrite', fh);
|
||||
rc = (
|
||||
n === (await getSyncHandle(fh,'xWrite'))
|
||||
.write(fh.sabView.subarray(0, n),
|
||||
{at: Number(offset64)})
|
||||
) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE;
|
||||
}catch(e){
|
||||
error("xWrite():",e,fh);
|
||||
state.s11n.storeException(1,e);
|
||||
rc = GetSyncHandleError.convertRc(e,state.sq3Codes.SQLITE_IOERR_WRITE);
|
||||
}
|
||||
await releaseImplicitLock(fh);
|
||||
storeAndNotify('xWrite',rc);
|
||||
}
|
||||
}/*vfsAsyncImpls*/;
|
||||
|
||||
const initS11n = ()=>{
|
||||
/**
|
||||
ACHTUNG: this code is 100% duplicated in the other half of this
|
||||
proxy! The documentation is maintained in the "synchronous half".
|
||||
*/
|
||||
if(state.s11n) return state.s11n;
|
||||
const textDecoder = new TextDecoder(),
|
||||
textEncoder = new TextEncoder('utf-8'),
|
||||
viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
|
||||
viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
|
||||
state.s11n = Object.create(null);
|
||||
const TypeIds = Object.create(null);
|
||||
TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
|
||||
TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
|
||||
TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
|
||||
TypeIds.string = { id: 4 };
|
||||
const getTypeId = (v)=>(
|
||||
TypeIds[typeof v]
|
||||
|| toss("Maintenance required: this value type cannot be serialized.",v)
|
||||
);
|
||||
const getTypeIdById = (tid)=>{
|
||||
switch(tid){
|
||||
case TypeIds.number.id: return TypeIds.number;
|
||||
case TypeIds.bigint.id: return TypeIds.bigint;
|
||||
case TypeIds.boolean.id: return TypeIds.boolean;
|
||||
case TypeIds.string.id: return TypeIds.string;
|
||||
default: toss("Invalid type ID:",tid);
|
||||
}
|
||||
};
|
||||
state.s11n.deserialize = function(clear=false){
|
||||
const argc = viewU8[0];
|
||||
const rc = argc ? [] : null;
|
||||
if(argc){
|
||||
const typeIds = [];
|
||||
let offset = 1, i, n, v;
|
||||
for(i = 0; i < argc; ++i, ++offset){
|
||||
typeIds.push(getTypeIdById(viewU8[offset]));
|
||||
}
|
||||
for(i = 0; i < argc; ++i){
|
||||
const t = typeIds[i];
|
||||
if(t.getter){
|
||||
v = viewDV[t.getter](offset, state.littleEndian);
|
||||
offset += t.size;
|
||||
}else{/*String*/
|
||||
n = viewDV.getInt32(offset, state.littleEndian);
|
||||
offset += 4;
|
||||
v = textDecoder.decode(viewU8.slice(offset, offset+n));
|
||||
offset += n;
|
||||
}
|
||||
rc.push(v);
|
||||
}
|
||||
}
|
||||
if(clear) viewU8[0] = 0;
|
||||
//log("deserialize:",argc, rc);
|
||||
return rc;
|
||||
};
|
||||
state.s11n.serialize = function(...args){
|
||||
if(args.length){
|
||||
//log("serialize():",args);
|
||||
const typeIds = [];
|
||||
let i = 0, offset = 1;
|
||||
viewU8[0] = args.length & 0xff /* header = # of args */;
|
||||
for(; i < args.length; ++i, ++offset){
|
||||
/* Write the TypeIds.id value into the next args.length
|
||||
bytes. */
|
||||
typeIds.push(getTypeId(args[i]));
|
||||
viewU8[offset] = typeIds[i].id;
|
||||
}
|
||||
for(i = 0; i < args.length; ++i) {
|
||||
/* Deserialize the following bytes based on their
|
||||
corresponding TypeIds.id from the header. */
|
||||
const t = typeIds[i];
|
||||
if(t.setter){
|
||||
viewDV[t.setter](offset, args[i], state.littleEndian);
|
||||
offset += t.size;
|
||||
}else{/*String*/
|
||||
const s = textEncoder.encode(args[i]);
|
||||
viewDV.setInt32(offset, s.byteLength, state.littleEndian);
|
||||
offset += 4;
|
||||
viewU8.set(s, offset);
|
||||
offset += s.byteLength;
|
||||
}
|
||||
}
|
||||
//log("serialize() result:",viewU8.slice(0,offset));
|
||||
}else{
|
||||
viewU8[0] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
state.s11n.storeException = state.asyncS11nExceptions
|
||||
? ((priority,e)=>{
|
||||
if(priority<=state.asyncS11nExceptions){
|
||||
state.s11n.serialize([e.name,': ',e.message].join(""));
|
||||
}
|
||||
})
|
||||
: ()=>{};
|
||||
|
||||
return state.s11n;
|
||||
}/*initS11n()*/;
|
||||
}/*xLock() and xUnlock() impls*/
|
||||
|
||||
const waitLoop = async function f(){
|
||||
const opHandlers = Object.create(null);
|
||||
for(let k of Object.keys(state.opIds)){
|
||||
const vi = vfsAsyncImpls[k];
|
||||
if(!vi) continue;
|
||||
const o = Object.create(null);
|
||||
opHandlers[state.opIds[k]] = o;
|
||||
o.key = k;
|
||||
o.f = vi;
|
||||
if( !f.inited ){
|
||||
f.inited = true;
|
||||
f.opHandlers = Object.create(null);
|
||||
for(let k of Object.keys(state.opIds)){
|
||||
const vi = vfsAsyncImpls[k];
|
||||
if(!vi) continue;
|
||||
const o = Object.create(null);
|
||||
f.opHandlers[state.opIds[k]] = o;
|
||||
o.key = k;
|
||||
o.f = vi;
|
||||
}
|
||||
}
|
||||
const opIds = state.opIds;
|
||||
const opView = state.sabOPView;
|
||||
const slotWhichOp = opIds.whichOp;
|
||||
const idleWaitTime = state.asyncIdleWaitTime;
|
||||
const hasWaitAsync = !!Atomics.waitAsync;
|
||||
//#if 0
|
||||
error("waitLoop init: isWebLocker",isWebLocker,
|
||||
"idleWaitTime",idleWaitTime,
|
||||
"hasWaitAsync",hasWaitAsync);
|
||||
//#/if
|
||||
while(!flagAsyncShutdown){
|
||||
try {
|
||||
if('not-equal'!==Atomics.wait(
|
||||
state.sabOPView, state.opIds.whichOp, 0, state.asyncIdleWaitTime
|
||||
)){
|
||||
/* Maintenance note: we compare against 'not-equal' because
|
||||
let opId;
|
||||
if( hasWaitAsync ){
|
||||
opId = Atomics.load(opView, slotWhichOp);
|
||||
if( 0===opId ){
|
||||
const rv = Atomics.waitAsync(opView, slotWhichOp, 0,
|
||||
idleWaitTime);
|
||||
if( rv.async ) await rv.value;
|
||||
await releaseImplicitLocks();
|
||||
continue;
|
||||
}
|
||||
}else{
|
||||
/**
|
||||
For browsers without Atomics.waitAsync(), we require
|
||||
the legacy implementation. Browser versions where
|
||||
waitAsync() arrived:
|
||||
|
||||
https://github.com/tomayac/sqlite-wasm/issues/12
|
||||
Chrome: 90 (2021-04-13)
|
||||
Firefox: 145 (2025-11-11)
|
||||
Safari: 16.4 (2023-03-27)
|
||||
|
||||
is reporting that this occasionally, under high loads,
|
||||
returns 'ok', which leads to the whichOp being 0 (which
|
||||
isn't a valid operation ID and leads to an exception,
|
||||
along with a corresponding ugly console log
|
||||
message). Unfortunately, the conditions for that cannot
|
||||
be reliably reproduced. The only place in our code which
|
||||
writes a 0 to the state.opIds.whichOp SharedArrayBuffer
|
||||
index is a few lines down from here, and that instance
|
||||
is required in order for clear communication between
|
||||
the sync half of this proxy and this half.
|
||||
The "opfs" VFS was not born until Chrome was somewhere in
|
||||
the v104-108 range (Summer/Autumn 2022) and did not work
|
||||
with Safari < v17 (2023-09-18) due to a WebKit bug which
|
||||
restricted OPFS access from sub-Workers.
|
||||
|
||||
The waitAsync() counterpart of this block can be used by
|
||||
both "opfs" and "opfs-wl", whereas this block can only be
|
||||
used by "opfs". Performance comparisons between the two
|
||||
in high-contention tests have been indecisive.
|
||||
*/
|
||||
await releaseImplicitLocks();
|
||||
continue;
|
||||
if('not-equal'!==Atomics.wait(
|
||||
state.sabOPView, slotWhichOp, 0, state.asyncIdleWaitTime
|
||||
)){
|
||||
/* Maintenance note: we compare against 'not-equal' because
|
||||
|
||||
https://github.com/tomayac/sqlite-wasm/issues/12
|
||||
|
||||
is reporting that this occasionally, under high loads,
|
||||
returns 'ok', which leads to the whichOp being 0 (which
|
||||
isn't a valid operation ID and leads to an exception,
|
||||
along with a corresponding ugly console log
|
||||
message). Unfortunately, the conditions for that cannot
|
||||
be reliably reproduced. The only place in our code which
|
||||
writes a 0 to the state.opIds.whichOp SharedArrayBuffer
|
||||
index is a few lines down from here, and that instance
|
||||
is required in order for clear communication between
|
||||
the sync half of this proxy and this half.
|
||||
|
||||
Much later (2026-03-07): that phenomenon is apparently
|
||||
called a spurious wakeup.
|
||||
*/
|
||||
await releaseImplicitLocks();
|
||||
continue;
|
||||
}
|
||||
opId = Atomics.load(state.sabOPView, slotWhichOp);
|
||||
}
|
||||
const opId = Atomics.load(state.sabOPView, state.opIds.whichOp);
|
||||
Atomics.store(state.sabOPView, state.opIds.whichOp, 0);
|
||||
const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId);
|
||||
Atomics.store(opView, slotWhichOp, 0);
|
||||
const hnd = f.opHandlers[opId]?.f ?? toss("No waitLoop handler for whichOp #",opId);
|
||||
const args = state.s11n.deserialize(
|
||||
true /* clear s11n to keep the caller from confusing this with
|
||||
an exception string written by the upcoming
|
||||
operation */
|
||||
) || [];
|
||||
//warn("waitLoop() whichOp =",opId, hnd, args);
|
||||
if(hnd.f) await hnd.f(...args);
|
||||
else error("Missing callback for opId",opId);
|
||||
//error("waitLoop() whichOp =",opId, f.opHandlers[opId].key, args);
|
||||
await hnd(...args);
|
||||
}catch(e){
|
||||
error('in waitLoop():',e);
|
||||
error('in waitLoop():', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -756,6 +922,7 @@ const installAsyncProxy = function(){
|
||||
navigator.storage.getDirectory().then(function(d){
|
||||
state.rootDir = d;
|
||||
globalThis.onmessage = function({data}){
|
||||
//log(globalThis.location.href,"onmessage()",data);
|
||||
switch(data.type){
|
||||
case 'opfs-async-init':{
|
||||
/* Receive shared state from synchronous partner */
|
||||
@@ -771,6 +938,7 @@ const installAsyncProxy = function(){
|
||||
}
|
||||
});
|
||||
initS11n();
|
||||
//warn("verbosity =",opt.verbose, state.verbose);
|
||||
log("init state",state);
|
||||
wPost('opfs-async-inited');
|
||||
waitLoop();
|
||||
@@ -782,22 +950,27 @@ const installAsyncProxy = function(){
|
||||
flagAsyncShutdown = false;
|
||||
waitLoop();
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
};
|
||||
wPost('opfs-async-loaded');
|
||||
}).catch((e)=>error("error initializing OPFS asyncer:",e));
|
||||
}/*installAsyncProxy()*/;
|
||||
if(!globalThis.SharedArrayBuffer){
|
||||
if(globalThis.window === globalThis){
|
||||
wPost('opfs-unavailable',
|
||||
"This code cannot run from the main thread.",
|
||||
"Load it as a Worker from a separate Worker.");
|
||||
}else if(!globalThis.SharedArrayBuffer){
|
||||
wPost('opfs-unavailable', "Missing SharedArrayBuffer API.",
|
||||
"The server must emit the COOP/COEP response headers to enable that.");
|
||||
}else if(!globalThis.Atomics){
|
||||
wPost('opfs-unavailable', "Missing Atomics API.",
|
||||
"The server must emit the COOP/COEP response headers to enable that.");
|
||||
}else if(isWebLocker && !globalThis.Atomics.waitAsync){
|
||||
wPost('opfs-unavailable',"Missing required Atomics.waitSync() for "+vfsName);
|
||||
}else if(!globalThis.FileSystemHandle ||
|
||||
!globalThis.FileSystemDirectoryHandle ||
|
||||
!globalThis.FileSystemFileHandle ||
|
||||
!globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle ||
|
||||
!globalThis.FileSystemFileHandle?.prototype?.createSyncAccessHandle ||
|
||||
!navigator?.storage?.getDirectory){
|
||||
wPost('opfs-unavailable',"Missing required OPFS APIs.");
|
||||
}else{
|
||||
@@ -25,9 +25,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
delete sqlite3.capi.KVVfsFile;
|
||||
}
|
||||
//#else
|
||||
//#@policy error
|
||||
//#@ policy error
|
||||
//#savepoint begin
|
||||
//#define kvvfs-v2-added-in=3.52.0
|
||||
//#define kvvfs-v2-added-in "3.52.0"
|
||||
|
||||
/**
|
||||
kvvfs - the Key/Value VFS - is an SQLite3 VFS which delegates
|
||||
storage of its pages and metadata to a key-value store.
|
||||
@@ -85,6 +86,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
backups.
|
||||
*/
|
||||
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
if( sqlite3.config.disable?.vfs?.kvvfs ){
|
||||
return;
|
||||
}
|
||||
'use strict';
|
||||
const capi = sqlite3.capi,
|
||||
sqlite3_kvvfs_methods = capi.sqlite3_kvvfs_methods,
|
||||
@@ -209,17 +213,21 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
and recreating it whenever a property index might be invalidated.
|
||||
*/
|
||||
class KVVfsStorage {
|
||||
#map;
|
||||
#keys;
|
||||
#getKeys(){return this.#keys ??= Object.keys(this.#map);}
|
||||
#map = Object.create(null);
|
||||
#keys = null;
|
||||
#size = 0;
|
||||
|
||||
constructor(){
|
||||
this.clear();
|
||||
}
|
||||
|
||||
#getKeys(){
|
||||
return this.#keys ??= Object.keys(this.#map);
|
||||
}
|
||||
|
||||
key(n){
|
||||
const k = this.#getKeys();
|
||||
return n<k.length ? k[n] : null;
|
||||
if(n < 0 || n >= this.#size) return null;
|
||||
return this.#getKeys()[n];
|
||||
}
|
||||
|
||||
getItem(k){
|
||||
@@ -227,14 +235,17 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
}
|
||||
|
||||
setItem(k,v){
|
||||
if( !hop(this.#map, k) ){
|
||||
if( !(k in this.#map) ){
|
||||
++this.#size;
|
||||
this.#keys = null;
|
||||
}
|
||||
this.#map[k] = ''+v;
|
||||
}
|
||||
|
||||
removeItem(k){
|
||||
if( delete this.#map[k] ){
|
||||
if( k in this.#map ){
|
||||
delete this.#map[k];
|
||||
--this.#size;
|
||||
this.#keys = null;
|
||||
}
|
||||
}
|
||||
@@ -242,10 +253,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
clear(){
|
||||
this.#map = Object.create(null);
|
||||
this.#keys = null;
|
||||
this.#size = 0;
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.#getKeys().length;
|
||||
return this.#size;
|
||||
}
|
||||
}/*KVVfsStorage*/;
|
||||
|
||||
@@ -569,7 +581,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
? cache.storagePool[zClass]
|
||||
: cache.storagePool[wasm.cstrToJs(zClass)];
|
||||
|
||||
//#if nope
|
||||
//#if 0
|
||||
// fileForDb() works but we don't have a current need for it.
|
||||
/**
|
||||
Expects an (sqlite3*). Uses sqlite3_file_control() to extract its
|
||||
@@ -612,7 +624,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
//#endif nope
|
||||
//#/if
|
||||
|
||||
const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKey;
|
||||
/**
|
||||
@@ -956,7 +968,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
return 0;
|
||||
}
|
||||
|
||||
//#if nope
|
||||
//#if 0
|
||||
// these impls work but there's currently no pressing need _not_ use
|
||||
// the native impls.
|
||||
xCurrentTime: function(pVfs,pOut){
|
||||
@@ -968,7 +980,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
wasm.poke64(pOut, (2440587.5 * 86400000) + Date.now());
|
||||
return 0;
|
||||
}
|
||||
//#endif
|
||||
//#/if
|
||||
}/*.vfs*/,
|
||||
|
||||
/**
|
||||
@@ -1064,7 +1076,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
}
|
||||
},
|
||||
|
||||
//#if not nope
|
||||
//#if 0
|
||||
// We override xRead/xWrite only for logging/debugging. They
|
||||
// should otherwise be disabled (it's faster that way).
|
||||
xRead: function(pFile,pTgt,n,iOff64){
|
||||
@@ -1095,9 +1107,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
return cache.setError(e);
|
||||
}
|
||||
},
|
||||
//#endif nope
|
||||
//#/if
|
||||
|
||||
//#if nope
|
||||
//#if 0
|
||||
xTruncate: function(pFile,i64){},
|
||||
xFileSize: function(pFile,pi64Out){},
|
||||
xLock: function(pFile,iLock){},
|
||||
@@ -1105,7 +1117,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
xCheckReservedLock: function(pFile,piOut){},
|
||||
xSectorSize: function(pFile){},
|
||||
xDeviceCharacteristics: function(pFile){}
|
||||
//#endif
|
||||
//#/if
|
||||
}/*.ioDb*/,
|
||||
|
||||
ioJrnl:{
|
||||
@@ -1113,7 +1125,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
are copied as-is from the ioDb objects. Others are specific
|
||||
to journal files. */
|
||||
xClose: true,
|
||||
//#if nope
|
||||
//#if 0
|
||||
xRead: function(pFile,pTgt,n,iOff64){},
|
||||
xWrite: function(pFile,pSrc,n,iOff64){},
|
||||
xTruncate: function(pFile,i64){},
|
||||
@@ -1125,13 +1137,16 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
xCheckReservedLock: true,
|
||||
xSectorSize: true,
|
||||
xDeviceCharacteristics: true
|
||||
//#endif
|
||||
//#/if
|
||||
}/*.ioJrnl*/
|
||||
}/*methodOverrides*/;
|
||||
|
||||
//#if 0
|
||||
debug("pVfs and friends", pVfs, pIoDb, pIoJrnl,
|
||||
kvvfsMethods, capi.sqlite3_file.structInfo,
|
||||
KVVfsFile.structInfo);
|
||||
//#/if
|
||||
|
||||
try {
|
||||
util.assert( cache.buffer.n>1024*129, "Heap buffer is not large enough"
|
||||
/* Native is SQLITE_KVOS_SZ is 133073 as of this writing */ );
|
||||
@@ -1220,7 +1235,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
limitation which has since been overcome, but removal of
|
||||
JsStorageDb.prototype.clearStorage() would be a backwards compatibility
|
||||
break, so this function permits wiping the storage for those two
|
||||
cases even if they are opened. Use with case.
|
||||
cases even if they are opened. Use with care.
|
||||
*/
|
||||
const sqlite3_js_kvvfs_clear = function callee(which){
|
||||
if( ''===which ){
|
||||
@@ -1829,7 +1844,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
return jdb.storageSize(this.affirmOpen().dbFilename(), true);
|
||||
};
|
||||
}/*sqlite3.oo1.JsStorageDb*/
|
||||
//#endif not omit-oo1
|
||||
//#/if not omit-oo1
|
||||
|
||||
if( sqlite3.__isUnderTest && sqlite3.vtab ){
|
||||
/**
|
||||
@@ -1993,7 +2008,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
|
||||
}/* virtual table */
|
||||
|
||||
//#if nope
|
||||
//#if 0
|
||||
/**
|
||||
The idea here is a simpler wrapper for listening to kvvfs
|
||||
changes. Clients would override its onXyz() event methods
|
||||
@@ -2080,8 +2095,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
async onOpen(count){}
|
||||
async onClose(count){}
|
||||
}/*KvvfsListener*/;
|
||||
//#endif nope
|
||||
//#/if nope
|
||||
|
||||
})/*globalThis.sqlite3ApiBootstrap.initializers*/;
|
||||
//#savepoint rollback
|
||||
//#endif not omit-kvvfs
|
||||
//#/if not omit-kvvfs
|
||||
|
||||
@@ -55,6 +55,10 @@
|
||||
*/
|
||||
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
'use strict';
|
||||
if( sqlite3.config.disable?.vfs?.['opfs-sahpool'] ){
|
||||
return;
|
||||
}
|
||||
|
||||
const toss = sqlite3.util.toss;
|
||||
const toss3 = sqlite3.util.toss3;
|
||||
const initPromises = Object.create(null) /* cache of (name:result) of VFS init results */;
|
||||
@@ -1467,4 +1471,4 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
The OPFS SAH Pool VFS parts are elided from builds targeting
|
||||
node.js.
|
||||
*/
|
||||
//#endif target:node
|
||||
//#/if target:node
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
//#if not target:node
|
||||
/*
|
||||
2026-02-20
|
||||
|
||||
The author disclaims copyright to this source code. In place of a
|
||||
legal notice, here is a blessing:
|
||||
|
||||
* May you do good and not evil.
|
||||
* May you find forgiveness for yourself and forgive others.
|
||||
* May you share freely, never taking more than you give.
|
||||
|
||||
***********************************************************************
|
||||
|
||||
This file is a reimplementation of the "opfs" VFS (as distinct from
|
||||
"opfs-sahpool") which uses WebLocks for locking instead of a bespoke
|
||||
Atomics.wait()/notify() protocol. This file holds the "synchronous
|
||||
half" of the VFS, whereas it shares the "asynchronous half" with the
|
||||
"opfs" VFS.
|
||||
|
||||
Testing has failed to show any genuine functional difference between
|
||||
these VFSes other than "opfs-wl" being able to dole out xLock()
|
||||
requests in a strictly FIFO manner by virtue of WebLocks being
|
||||
globally managed by the browser. This tends to lead to, but does not
|
||||
guaranty, fairer distribution of locks. Differences are unlikely to
|
||||
be noticed except, perhaps, under very high contention.
|
||||
|
||||
This file is intended to be appended to the main sqlite3 JS
|
||||
deliverable somewhere after opfs-common-shared.c-pp.js.
|
||||
*/
|
||||
'use strict';
|
||||
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
if( !sqlite3.opfs || sqlite3.config.disable?.vfs?.['opfs-wl'] ){
|
||||
return;
|
||||
}
|
||||
const util = sqlite3.util,
|
||||
toss = sqlite3.util.toss;
|
||||
const opfsUtil = sqlite3.opfs;
|
||||
const vfsName = 'opfs-wl';
|
||||
/**
|
||||
installOpfsWlVfs() returns a Promise which, on success, installs an
|
||||
sqlite3_vfs named "opfs-wl", suitable for use with all sqlite3 APIs
|
||||
which accept a VFS. It is intended to be called via
|
||||
sqlite3ApiBootstrap.initializers or an equivalent mechanism.
|
||||
|
||||
This VFS is essentially identical to the "opfs" VFS but uses
|
||||
WebLocks for its xLock() and xUnlock() implementations.
|
||||
|
||||
Quirks specific to this VFS:
|
||||
|
||||
- The (officially undocumented) 'opfs-wl-disable' URL
|
||||
argument will disable OPFS, making this function a no-op.
|
||||
|
||||
Aside from locking differences in the VFSes, this function
|
||||
otherwise behaves the same as
|
||||
sqlite3-vfs-opfs.c-pp.js:installOpfsVfs().
|
||||
*/
|
||||
const installOpfsWlVfs = async function(options){
|
||||
options = opfsUtil.initOptions(vfsName,options);
|
||||
if( !options ) return sqlite3;
|
||||
const capi = sqlite3.capi,
|
||||
state = opfsUtil.createVfsState(),
|
||||
opfsVfs = state.vfs,
|
||||
metrics = opfsVfs.metrics.counters,
|
||||
mTimeStart = opfsVfs.mTimeStart,
|
||||
mTimeEnd = opfsVfs.mTimeEnd,
|
||||
opRun = opfsVfs.opRun,
|
||||
debug = (...args)=>sqlite3.config.debug(vfsName+":",...args),
|
||||
warn = (...args)=>sqlite3.config.warn(vfsName+":",...args),
|
||||
__openFiles = opfsVfs.__openFiles;
|
||||
|
||||
//debug("state",JSON.stringify(options));
|
||||
/*
|
||||
At this point, createVfsState() has populated:
|
||||
|
||||
- state: the configuration object we share with the async proxy.
|
||||
|
||||
- opfsVfs: an sqlite3_vfs instance with lots of JS state attached
|
||||
to it.
|
||||
|
||||
with any code common to both the "opfs" and "opfs-wl" VFSes. Now
|
||||
comes the VFS-dependent work...
|
||||
*/
|
||||
return opfsVfs.bindVfs(util.nu({
|
||||
xLock: function(pFile,lockType){
|
||||
mTimeStart('xLock');
|
||||
//debug("xLock()...");
|
||||
const f = __openFiles[pFile];
|
||||
const rc = opRun('xLock', pFile, lockType);
|
||||
if( !rc ) f.lockType = lockType;
|
||||
mTimeEnd();
|
||||
return rc;
|
||||
},
|
||||
xUnlock: function(pFile,lockType){
|
||||
mTimeStart('xUnlock');
|
||||
const f = __openFiles[pFile];
|
||||
const rc = opRun('xUnlock', pFile, lockType);
|
||||
if( !rc ) f.lockType = lockType;
|
||||
mTimeEnd();
|
||||
return rc;
|
||||
}
|
||||
}), function(sqlite3, vfs){
|
||||
/* Post-VFS-registration initialization... */
|
||||
if(sqlite3.oo1){
|
||||
const OpfsWlDb = function(...args){
|
||||
const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args);
|
||||
opt.vfs = vfs.$zName;
|
||||
sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
|
||||
};
|
||||
OpfsWlDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
|
||||
sqlite3.oo1.OpfsWlDb = OpfsWlDb;
|
||||
OpfsWlDb.importDb = opfsUtil.importDb;
|
||||
/* The "opfs" VFS variant adds a
|
||||
oo1.DB.dbCtorHelper.setVfsPostOpenCallback() callback to set
|
||||
a high busy_timeout. That was a design mis-decision and is
|
||||
inconsistent with sqlite3_open() and friends, but is retained
|
||||
against the risk of introducing regressions if it's removed.
|
||||
This variant does not repeat that mistake.
|
||||
*/
|
||||
}
|
||||
})/*bindVfs()*/;
|
||||
}/*installOpfsWlVfs()*/;
|
||||
globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
|
||||
return installOpfsWlVfs().catch((e)=>{
|
||||
sqlite3.config.warn("Ignoring inability to install the",vfsName,"sqlite3_vfs:",e);
|
||||
});
|
||||
});
|
||||
}/*sqlite3ApiBootstrap.initializers.push()*/);
|
||||
//#/if target:node
|
||||
File diff suppressed because it is too large
Load Diff
@@ -571,6 +571,7 @@ SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_enum_json,(void)){
|
||||
DefInt(SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE);
|
||||
DefInt(SQLITE_DBCONFIG_ENABLE_COMMENTS);
|
||||
DefInt(SQLITE_DBCONFIG_MAX);
|
||||
DefInt(SQLITE_DBCONFIG_FP_DIGITS);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(dbStatus){
|
||||
@@ -698,6 +699,8 @@ SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_enum_json,(void)){
|
||||
DefInt(SQLITE_MAX_TRIGGER_DEPTH);
|
||||
DefInt(SQLITE_LIMIT_WORKER_THREADS);
|
||||
DefInt(SQLITE_MAX_WORKER_THREADS);
|
||||
DefInt(SQLITE_LIMIT_PARSER_DEPTH);
|
||||
DefInt(SQLITE_MAX_PARSER_DEPTH);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(openFlags) {
|
||||
@@ -731,6 +734,7 @@ SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_enum_json,(void)){
|
||||
DefInt(SQLITE_PREPARE_PERSISTENT);
|
||||
DefInt(SQLITE_PREPARE_NORMALIZE);
|
||||
DefInt(SQLITE_PREPARE_NO_VTAB);
|
||||
DefInt(SQLITE_PREPARE_FROM_DDL);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(resultCodes) {
|
||||
@@ -1640,6 +1644,7 @@ SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_ip,
|
||||
case SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE:
|
||||
case SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE:
|
||||
case SQLITE_DBCONFIG_ENABLE_COMMENTS:
|
||||
case SQLITE_DBCONFIG_FP_DIGITS:
|
||||
return sqlite3_db_config(pDb, op, arg1, pArg2);
|
||||
default: return SQLITE_MISUSE;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
*/
|
||||
//#if not defined target:es6-module
|
||||
'use strict';
|
||||
//#endif
|
||||
//#/if
|
||||
/**
|
||||
Configures an sqlite3 Worker API #1 Worker such that it can be
|
||||
manipulated via a Promise-based interface and returns a factory
|
||||
@@ -278,13 +278,13 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = {
|
||||
}
|
||||
}
|
||||
return new Worker(theJs + globalThis.location.search);
|
||||
//#endif
|
||||
//#/if
|
||||
}
|
||||
//#if not target:es6-module
|
||||
.bind({
|
||||
currentScript: globalThis?.document?.currentScript
|
||||
})
|
||||
//#endif
|
||||
//#/if
|
||||
,
|
||||
onerror: (...args)=>console.error('sqlite3Worker1Promiser():',...args)
|
||||
}/*defaultConfig*/;
|
||||
@@ -349,7 +349,7 @@ globalThis.sqlite3Worker1Promiser.v2.defaultConfig =
|
||||
*/
|
||||
export default sqlite3Worker1Promiser.v2;
|
||||
delete globalThis.sqlite3Worker1Promiser;
|
||||
//#endif /* target:es6-module */
|
||||
//#/if /* target:es6-module */
|
||||
//#else
|
||||
/* Built with the omit-oo1 flag. */
|
||||
//#endif if not omit-oo1
|
||||
//#/if if not omit-oo1
|
||||
|
||||
@@ -49,8 +49,8 @@ import sqlite3InitModule from './sqlite3.mjs';
|
||||
//console.warn("worker1 theJs =",theJs);
|
||||
importScripts(theJs);
|
||||
}
|
||||
//#endif
|
||||
//#/if
|
||||
sqlite3InitModule().then(sqlite3 => sqlite3.initWorker1API());
|
||||
//#else
|
||||
/* Built with the omit-oo1 flag. */
|
||||
//#endif if not omit-oo1
|
||||
//#/if if not omit-oo1
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@
|
||||
<title>Worker1-promiser (ESM) tests</title>
|
||||
//#else
|
||||
<title>Worker1-promiser tests</title>
|
||||
//#endif
|
||||
//#/if
|
||||
</head>
|
||||
<body>
|
||||
<header id='titlebar'><span>worker-promise tests</span></header>
|
||||
@@ -37,6 +37,6 @@
|
||||
//#else
|
||||
<script src="jswasm/sqlite3-worker1-promiser.js"></script>
|
||||
<script src="demo-worker1-promiser.js"></script>
|
||||
//#endif
|
||||
//#/if
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -19,7 +19,7 @@ import {default as promiserFactory} from "./jswasm/sqlite3-worker1-promiser.mjs"
|
||||
"use strict";
|
||||
const promiserFactory = globalThis.sqlite3Worker1Promiser.v2;
|
||||
delete globalThis.sqlite3Worker1Promiser;
|
||||
//#endif
|
||||
//#/if
|
||||
(async function(){
|
||||
const T = globalThis.SqliteTestUtil;
|
||||
const eOutput = document.querySelector('#test-output');
|
||||
@@ -53,7 +53,7 @@ delete globalThis.sqlite3Worker1Promiser;
|
||||
before workerPromise is set. */
|
||||
console.warn("This is the v2 interface - you don't need an onready() function.");
|
||||
},
|
||||
//#endif
|
||||
//#/if
|
||||
debug: 1 ? undefined : (...args)=>console.debug('worker debug',...args),
|
||||
onunhandled: function(ev){
|
||||
error("Unhandled worker message:",ev.data);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user