Compare commits
566 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| adbb756155 | |||
| 658908a32f | |||
| b3b2695d4b | |||
| 775e4b7073 | |||
| 4bee65dcb9 | |||
| 4d0611c1eb | |||
| 10fb1e3077 | |||
| db84cdb747 | |||
| 990d861038 | |||
| 23e57bc7cd | |||
| 7d49eb1dff | |||
| df59c5da78 | |||
| 643fb6cf4b | |||
| c652e34317 | |||
| c94f97fac7 | |||
| 1f9ae1f780 | |||
| 612bee531f | |||
| de702aebb9 | |||
| e45418c5f5 | |||
| a0857f321c | |||
| 450a90097f | |||
| e58c1a987c | |||
| d0c4a5853b | |||
| b55d6f3754 | |||
| c9ccd27045 | |||
| 5e842d0ece | |||
| 3fab5d2a40 | |||
| 761118497a | |||
| e54142009c | |||
| 1131eddbf9 | |||
| 3212ab33d4 | |||
| 49c70b88bc | |||
| 77c5456d7f | |||
| 5b23d027cf | |||
| 71848d362d | |||
| 1ac0d9bbf9 | |||
| 213262fa53 | |||
| 5c8cd31246 | |||
| 1d8eb1d9b6 | |||
| 4fd4e8f066 | |||
| c79b1f7d6c | |||
| 4b4df2ee1e | |||
| 1d1cd36428 | |||
| a5cc01681e | |||
| 561e28b75b | |||
| bfb33d476b | |||
| fabd481c76 | |||
| 2c49785000 | |||
| 60229c2971 | |||
| 836231691e | |||
| c4e7964fd6 | |||
| f5d29d6424 | |||
| b4142c491e | |||
| a943c0e973 | |||
| 6f8f9fbe25 | |||
| 87efa1bd79 | |||
| 389926bd3c | |||
| f202df5857 | |||
| e17a469d74 | |||
| 8f03e92c41 | |||
| 583644e660 | |||
| 15357aaf10 | |||
| 49bf78685e | |||
| 840199ee18 | |||
| 713ee08d33 | |||
| 9b7005feea | |||
| 33db5b73e5 | |||
| 3b440ce573 | |||
| 8e272dd9a7 | |||
| 46a395ff3d | |||
| 619f1f7083 | |||
| 84075044de | |||
| fb288a432c | |||
| 40b6eace0e | |||
| a5070d083c | |||
| ca77c585be | |||
| e4445f04c5 | |||
| 4b736ce2f8 | |||
| 447fd1bf29 | |||
| 1f567aef4c | |||
| 40573642e8 | |||
| c46074808e | |||
| 8fd91361e6 | |||
| cc11fb3bb2 | |||
| cb3d6d14fe | |||
| d3285e67a8 | |||
| e0e67f1a73 | |||
| 2d98df1536 | |||
| e702822708 | |||
| ca0b14a490 | |||
| e66806de33 | |||
| 815905544d | |||
| e822005684 | |||
| bb56ddc4e3 | |||
| ec13fcecd2 | |||
| d4dc275ebc | |||
| f4a613aa46 | |||
| d50161a3cd | |||
| 4eb307cf46 | |||
| 0af76f6caa | |||
| 8c4e315e19 | |||
| 4720c16223 | |||
| 6f3a679a7d | |||
| 4b9def1903 | |||
| 9dff6ac96e | |||
| 4dd1a202de | |||
| cd8aee7946 | |||
| 90942efd21 | |||
| bcc496e526 | |||
| ce83fd80ad | |||
| 43eb1d4d22 | |||
| abdfd3312a | |||
| e5fba7f094 | |||
| 35b9709d46 | |||
| 4b12a38ffb | |||
| 9a341c1edb | |||
| 9f78d22870 | |||
| bc2101b44e | |||
| 6444f694a6 | |||
| a87d7c00f4 | |||
| 428c2656ff | |||
| 9ab2a2af1b | |||
| 2f47a58c5d | |||
| 644c42f3a4 | |||
| aef151ada7 | |||
| 0ad6c6c440 | |||
| 4b93124b6b | |||
| 1d49bbc25e | |||
| 4b4d07253f | |||
| c34d561940 | |||
| 22c99e01ff | |||
| 0a10cdb663 | |||
| 5df3efe6f4 | |||
| cc4a404048 | |||
| 403c153dfa | |||
| 784bceae33 | |||
| 92730d21ff | |||
| 22b08034aa | |||
| c219d3002f | |||
| 9f9ee82b62 | |||
| 67429703bf | |||
| c0420cd8e4 | |||
| e46b51e234 | |||
| 8a2be4b739 | |||
| ad4b08dcab | |||
| bba40a9c9b | |||
| ef5d9952eb | |||
| 0b5f8766fd | |||
| 03c0ac435e | |||
| 242a9347f8 | |||
| 8fe0385661 | |||
| d3c9cce43c | |||
| bcf1c8d6e3 | |||
| 55f258bd27 | |||
| bb4428e605 | |||
| b1e9111249 | |||
| 904abc85cf | |||
| 0ed2541168 | |||
| a0cc06b66b | |||
| 2b112e0907 | |||
| b0a1ed2980 | |||
| 6e8afa79e7 | |||
| db4d8946ef | |||
| f395df1307 | |||
| 02e38b4d21 | |||
| 6789770822 | |||
| d964b8cfa3 | |||
| 634a915132 | |||
| 31f6052f03 | |||
| f8bd7df996 | |||
| 03380a55a5 | |||
| d32e21ac95 | |||
| 9f44bc5ea0 | |||
| 02ceb91875 | |||
| 918efe3cce | |||
| 01f2907d48 | |||
| 6e92494955 | |||
| 9924b07cc2 | |||
| fb795dbe73 | |||
| 7dfb1431dc | |||
| bd91ba71d5 | |||
| ce36cccd36 | |||
| 6693e78772 | |||
| c00c0a8392 | |||
| 38c8014940 | |||
| 35f37ec959 | |||
| 7f56d23f9c | |||
| 4056656047 | |||
| 91d0ffa576 | |||
| 683df60dbf | |||
| 337fce82f0 | |||
| f256886b8e | |||
| 8511ee1e19 | |||
| 766389bd65 | |||
| add73c831b | |||
| 8d41b94bb4 | |||
| 3b81f93532 | |||
| 19a3f74bdc | |||
| 174dabe096 | |||
| 683c563241 | |||
| 65bc1f0ff4 | |||
| ae66a8d760 | |||
| ce46f82bc4 | |||
| 4461335b00 | |||
| 1b7b75679a | |||
| 93c6b19f92 | |||
| 6c2f5dabf4 | |||
| 89bc8e8a34 | |||
| 93ecf30068 | |||
| 6c26b5db71 | |||
| 28ec7be607 | |||
| a1dbb7a93e | |||
| 97601244e2 | |||
| 22546e1d7e | |||
| 586a649698 | |||
| e2bb4cf412 | |||
| 76f1fe2942 | |||
| 5266818aed | |||
| ce43bf85fa | |||
| 4f3ab29aab | |||
| d031267c17 | |||
| 48915e7c7d | |||
| 462289df52 | |||
| 63f1fb40be | |||
| 5eba66ad66 | |||
| c2cd606f8a | |||
| b304b0d165 | |||
| e29acd77c3 | |||
| 9334b9f1b8 | |||
| c0160fa11a | |||
| 85fb541ac0 | |||
| 3262f769ee | |||
| 2f2b5d7b20 | |||
| 9d5f00706f | |||
| 1debc25ef8 | |||
| 9bf37cc3eb | |||
| b4f6c09e60 | |||
| 567dcbb5db | |||
| 91479c6b10 | |||
| 50ef2f7813 | |||
| 1241fe1450 | |||
| ee9f4acb10 | |||
| b6306586ec | |||
| b58b45d48e | |||
| 6993f52d94 | |||
| 22b1bc9997 | |||
| 9d6b913098 | |||
| f1d5e9e913 | |||
| 2463c80b54 | |||
| db94b3c0d7 | |||
| c281beded3 | |||
| b8f8d021c9 | |||
| 7c74a3fba5 | |||
| 8ff58b4f41 | |||
| 4694b91988 | |||
| 316020580e | |||
| 9dd6979f3e | |||
| 3dbe1f2f96 | |||
| b4397ec831 | |||
| 9f83f4c563 | |||
| 768c364aaa | |||
| 6a59bb6057 | |||
| e8416526f0 | |||
| f2ec1d2560 | |||
| 4719c67753 | |||
| facbf34cbf | |||
| c4a22d6c14 | |||
| 973340c681 | |||
| 154e1082f1 | |||
| a4cb56e425 | |||
| a5f1fc25e3 | |||
| 77e1e0b4ef | |||
| 2348682e4e | |||
| 4ad869aa3f | |||
| 0e4ea1821f | |||
| 0baab04f8b | |||
| a781caa9ad | |||
| 1da8e7ac4f | |||
| 20e1f8a289 | |||
| f0ca498c61 | |||
| e62055f274 | |||
| 27aec2d1c1 | |||
| 30f9c25d9a | |||
| a63538027c | |||
| 68230ef670 | |||
| 76dc170938 | |||
| c14460f466 | |||
| d3a0fc5473 | |||
| 45d8831cad | |||
| b7ed25a86f | |||
| 91c8b974e5 | |||
| c4d6f31747 | |||
| d8b323f652 | |||
| c340a0f717 | |||
| 72b4d638a5 | |||
| 853335af49 | |||
| 8679c33355 | |||
| 606b1bd153 | |||
| da0d872270 | |||
| 274db1fd84 | |||
| d855948547 | |||
| 56a4a2173e | |||
| ea375f1320 | |||
| 48c8405f99 | |||
| 44fdf80c28 | |||
| 05478c7149 | |||
| 7ae80c0bc1 | |||
| ec274603f1 | |||
| 16d127eb45 | |||
| fc6096b013 | |||
| 7845d312b1 | |||
| 92fa31bc5f | |||
| 7c07260353 | |||
| 6690972855 | |||
| 81b7e49d2d |
+52
-54
@@ -788,17 +788,21 @@ BCC = $(BCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
|
||||
!IF $(DEBUG)>1
|
||||
TCC = $(TCC) -MDd
|
||||
BCC = $(BCC) -MDd
|
||||
ZLIBCFLAGS = -nologo -MDd -W3 -O2 -Oy- -Zi
|
||||
!ELSE
|
||||
TCC = $(TCC) -MD
|
||||
BCC = $(BCC) -MD
|
||||
ZLIBCFLAGS = -nologo -MD -W3 -O2 -Oy- -Zi
|
||||
!ENDIF
|
||||
!ELSE
|
||||
!IF $(DEBUG)>1
|
||||
TCC = $(TCC) -MTd
|
||||
BCC = $(BCC) -MTd
|
||||
ZLIBCFLAGS = -nologo -MTd -W3 -O2 -Oy- -Zi
|
||||
!ELSE
|
||||
TCC = $(TCC) -MT
|
||||
BCC = $(BCC) -MT
|
||||
ZLIBCFLAGS = -nologo -MT -W3 -O2 -Oy- -Zi
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
@@ -1715,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
|
||||
@@ -1761,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
|
||||
@@ -1862,8 +1877,8 @@ $(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLIT
|
||||
/link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS)
|
||||
|
||||
# <<mark>>
|
||||
sqldiff.exe: $(TOP)\tool\sqldiff.c $(TOP)\ext\misc\sqlite3_stdio.h $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
|
||||
$(LTLINK) $(NO_WARN) -I$(TOP)\ext\misc $(TOP)\tool\sqldiff.c $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS)
|
||||
sqldiff.exe: $(TOP)\tool\sqldiff.c $(TOP)\ext\misc\sqlite3_stdio.h $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) $(TOP)\tool\winmain.c
|
||||
$(LTLINK) $(NO_WARN) -I$(TOP)\ext\misc $(TOP)\tool\sqldiff.c $(TOP)\ext\misc\sqlite3_stdio.c $(TOP)\tool\winmain.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS)
|
||||
|
||||
dbhash.exe: $(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
@@ -2362,7 +2377,7 @@ shell.c: $(SHELL_DEP) $(TOP)\tool\mkshellc.tcl $(JIM_TCLSH)
|
||||
$(JIM_TCLSH) $(TOP)\tool\mkshellc.tcl shell.c
|
||||
|
||||
zlib:
|
||||
pushd $(ZLIBDIR) && $(MAKE) /f win32\Makefile.msc clean $(ZLIBLIB) && popd
|
||||
pushd $(ZLIBDIR) && $(MAKE) /f win32\Makefile.msc clean $(ZLIBLIB) CFLAGS="$(ZLIBCFLAGS)" && popd
|
||||
|
||||
# Rules to build the extension objects.
|
||||
#
|
||||
@@ -2536,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.
|
||||
#
|
||||
@@ -2597,6 +2588,11 @@ devtest: srctree-check sourcetest
|
||||
mdevtest:
|
||||
$(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest
|
||||
|
||||
# Rerun test cases that failed on the previous testrunner invocation
|
||||
#
|
||||
retest:
|
||||
$(TCLSH_CMD) $(TOP)\test\testrunner.tcl retest
|
||||
|
||||
# Show all errors from the most reason testrunner invocation
|
||||
#
|
||||
errors:
|
||||
@@ -2631,8 +2627,14 @@ smoketest: $(TESTPROGS)
|
||||
@set PATH=$(LIBTCLPATH);$(PATH)
|
||||
.\testfixture.exe $(TOP)\test\main.test $(TESTOPTS)
|
||||
|
||||
shelltest: $(TESTPROGS)
|
||||
.\testfixture.exe $(TOP)\test\permutations.test shell
|
||||
# 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
|
||||
|
||||
sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) tclsqlite-ex.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(TOP)\ext\misc\sqlite3_stdio.h $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE_TCL_DEP)
|
||||
$(TCLSH_CMD) $(TOP)\tool\mkccode.tcl -DINCLUDE_SQLITE3_C $(TOP)\tool\sqlite3_analyzer.c.in > $@
|
||||
@@ -2651,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)
|
||||
@@ -2700,6 +2685,9 @@ showwal.exe: $(TOP)\tool\showwal.c $(SQLITE3C) $(SQLITE3H)
|
||||
showshm.exe: $(TOP)\tool\showshm.c
|
||||
$(LTLINK) $(NO_WARN) $(TOP)\tool\showshm.c /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
showtmlog.exe: $(TOP)\tool\showtmlog.c
|
||||
$(LTLINK) $(NO_WARN) $(TOP)\tool\showtmlog.c /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
index_usage.exe: $(TOP)\tool\index_usage.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
|
||||
$(TOP)\tool\index_usage.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
@@ -2737,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)
|
||||
@@ -2891,6 +2887,7 @@ env:
|
||||
@echo USE_STDCALL = $(USE_STDCALL)
|
||||
@echo USE_ZLIB = $(USE_ZLIB)
|
||||
@echo XCOMPILE = $(XCOMPILE)
|
||||
@echo ZLIBCFLAGS = $(ZLIBCFLAGS)
|
||||
@echo ZLIBDIR = $(ZLIBDIR)
|
||||
@echo ZLIBINCDIR = $(ZLIBINCDIR)
|
||||
@echo ZLIBLIBDIR = $(ZLIBLIBDIR)
|
||||
@@ -2925,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
|
||||
|
||||
@@ -282,7 +282,7 @@ DIST_FILES := \
|
||||
README.txt VERSION \
|
||||
auto.def autosetup configure tea \
|
||||
sqlite3.h sqlite3.c shell.c sqlite3ext.h \
|
||||
Makefile.in Makefile.msc Makefile.fallback \
|
||||
Makefile.in Makefile.msc Makefile.fallback make.bat \
|
||||
sqlite3.rc sqlite3rc.h Replace.cs \
|
||||
sqlite3.pc.in sqlite3.1
|
||||
|
||||
|
||||
@@ -672,17 +672,21 @@ BCC = $(BCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
|
||||
!IF $(DEBUG)>1
|
||||
TCC = $(TCC) -MDd
|
||||
BCC = $(BCC) -MDd
|
||||
ZLIBCFLAGS = -nologo -MDd -W3 -O2 -Oy- -Zi
|
||||
!ELSE
|
||||
TCC = $(TCC) -MD
|
||||
BCC = $(BCC) -MD
|
||||
ZLIBCFLAGS = -nologo -MD -W3 -O2 -Oy- -Zi
|
||||
!ENDIF
|
||||
!ELSE
|
||||
!IF $(DEBUG)>1
|
||||
TCC = $(TCC) -MTd
|
||||
BCC = $(BCC) -MTd
|
||||
ZLIBCFLAGS = -nologo -MTd -W3 -O2 -Oy- -Zi
|
||||
!ELSE
|
||||
TCC = $(TCC) -MT
|
||||
BCC = $(BCC) -MT
|
||||
ZLIBCFLAGS = -nologo -MT -W3 -O2 -Oy- -Zi
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
|
||||
+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/
|
||||
|
||||
+2
-2
@@ -1842,7 +1842,7 @@ proc proj-setup-autoreconfig {defName} {
|
||||
}
|
||||
|
||||
#
|
||||
# @prop-append-to defineName args...
|
||||
# @prop-define-append defineName args...
|
||||
#
|
||||
# A proxy for Autosetup's [define-append]. Appends all non-empty $args
|
||||
# to [define-append $defineName].
|
||||
@@ -1873,7 +1873,7 @@ proc proj-define-append {defineName args} {
|
||||
# but it is technically correct and still relevant on some
|
||||
# environments.
|
||||
#
|
||||
# See: proj-append-to
|
||||
# See: proj-define-append
|
||||
#
|
||||
proc proj-define-amend {args} {
|
||||
set defName ""
|
||||
|
||||
@@ -1138,6 +1138,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 +1194,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]} {
|
||||
|
||||
+10
-3
@@ -22,9 +22,10 @@ guidance on building for Windows.
|
||||
or <https://sqlite.org/tmp/tcl9.0.0.tar.gz>.
|
||||
<li>Untar the source archive. CD into the "unix/" subfolder
|
||||
of the source tree.
|
||||
<li>Run: `mkdir $HOME/local`
|
||||
<li>Run: `./configure --prefix=$HOME/local`
|
||||
<li>Run: `make install`
|
||||
<li>Run:   `mkdir $HOME/local`
|
||||
<li>Run:   `./configure --prefix=$HOME/local`<br>
|
||||
SQLite deliverable builds add:   `--static CFLAGS=-Os`
|
||||
<li>Run:   `make install`
|
||||
</ol>
|
||||
<p>
|
||||
As of 2024-10-25, TCL is not longer required for many
|
||||
@@ -41,6 +42,12 @@ guidance on building for Windows.
|
||||
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`.
|
||||
|
||||
6. Run the "`Makefile`" makefile with an appropriate target.
|
||||
Examples:
|
||||
<ul>
|
||||
|
||||
@@ -180,3 +180,39 @@ statically linked so that it does not depend on separate DLL:
|
||||
6. After your executable is built, you can verify that it does not
|
||||
depend on the TCL DLL by running:
|
||||
<blockquote><pre>dumpbin /dependents sqlite3_analyzer.exe</pre></blockquote>
|
||||
|
||||
## Linking Against ZLIB
|
||||
|
||||
Some feature (such as zip-file support in the CLI) require the ZLIB
|
||||
compression library. That library is more or less universally available
|
||||
on unix platforms, but is seldom provided on Windows. You will probably
|
||||
need to provide it yourself. Here the the steps needed:
|
||||
|
||||
1. Download the zlib-1.3.1.tar.gz tarball (or a similar version).
|
||||
Unpack the tarball sources. You can put them wherever you like.
|
||||
For the purposes of this document, let's assume you put the source
|
||||
tree in c:\\zlib-64. Note: If you are building for both x64 and
|
||||
x86, you will need separate builds of ZLIB for each, thus separate
|
||||
build directories.
|
||||
|
||||
2. Before building SQLite (as described above) first make these
|
||||
environment changes. The lead-programmer for SQLite (who writes
|
||||
these words) has BAT files named "env-x64.bat" and "env-x32.bat"
|
||||
and "env-arm64.bat" that make these changes, and he runs those
|
||||
BAT file whenever he starts a new shell. These are the settings
|
||||
needed:
|
||||
<blockquote>
|
||||
set USE_ZLIB=1<br>
|
||||
set BUILD_ZLIB=0<br>
|
||||
set ZLIBDIR=c:\\zlib-64
|
||||
</blockquote>
|
||||
|
||||
3. Because the settings in step 2 specify "BUILD_ZLIB=0", you will need
|
||||
to build the library at least once. I recommand:
|
||||
<blockquote>
|
||||
make clean sqlite3.exe BUILD_ZLIB=1
|
||||
</blockquote>
|
||||
|
||||
4. After making the environment changes specified in steps 1 through 3
|
||||
above, you then build and test SQLite as you normally would. The
|
||||
environment variable changes will cause ZLIB to be linked automatically.
|
||||
|
||||
+102
-57
@@ -4,6 +4,12 @@
|
||||
|
||||
<ul type=none>
|
||||
<li> 1. <a href=#overview>Overview</a>
|
||||
<ul type=none>
|
||||
<li> 1.1. <a href=#runtr>Running testrunner.tcl</a>
|
||||
<li> 1.2. <a href=#runviamake>Run using "make"</a>
|
||||
<li> 1.3. <a href=#outputs>Outputs from testrunner.tcl</a>
|
||||
<li> 1.4. <a href=#help>Built-in help</a>
|
||||
</ul>
|
||||
<li> 2. <a href=#binary_tests>Binary Tests</a>
|
||||
<ul type=none>
|
||||
<li> 2.1. <a href=#organization_tests>Organization of Tcl Tests</a>
|
||||
@@ -26,17 +32,40 @@
|
||||
|
||||
The testrunner.tcl program is a Tcl script used to run multiple SQLite
|
||||
tests in parallel, thus reducing testing time on multi-core machines.
|
||||
It supports the following types of tests:
|
||||
The testrunner.tcl supports running tests that based on `testfixture`,
|
||||
`sqlite3`, and `fuzzcheck`.
|
||||
|
||||
* Tcl test scripts.
|
||||
<a name="runtr"></a>
|
||||
## 1.1 Running testrunner.tcl
|
||||
|
||||
* Fuzzcheck tests, including using an external fuzzcheck database.
|
||||
The testrunner.tcl script is located in the "test" subdirectory of the
|
||||
SQLite source tree. So if your shell is current positioned at the top
|
||||
of the source tree, you would normally run the script using the command:
|
||||
"<tt>test/testrunner.tcl</tt>". On Windows, you have to specify the
|
||||
`tclsh` interpreter command first, like this:
|
||||
"<tt>tclsh test/testrunner.tcl</tt>".
|
||||
|
||||
* Tests run with `make` commands. Examples:
|
||||
- `make devtest`
|
||||
- `make releasetest`
|
||||
- `make sdevtest`
|
||||
- `make testrunner`
|
||||
In this document, we will assume that you are on a unix-like OS
|
||||
(not on Windows) and that your current directory is the root
|
||||
of the SQLite source tree, and so all invocations of the testrunner.tcl
|
||||
script will be of the form "<tt>test/testrunner.tcl</tt>". If you
|
||||
are in a different directory, then make appropriate adjustments to
|
||||
the path. On Windows, add the "<tt>tclsh</tt>" interpreter command
|
||||
up front.
|
||||
|
||||
<a name="runviamake"></a>
|
||||
## 1.2 Run using make
|
||||
|
||||
The standard Makefiles for SQLite include targets that invoke
|
||||
testrunner.tcl. So the following commands also run testrunner.tcl:
|
||||
|
||||
* `make devtest`
|
||||
* `make releasetest`
|
||||
* `make sdevtest`
|
||||
* `make testrunner`
|
||||
|
||||
<a name="outputs"></a>
|
||||
## 1.3 Outputs from testrunner.tcl
|
||||
|
||||
The testrunner.tcl program stores output of all tests and builds run in
|
||||
log file **testrunner.log**, created in the current working directory.
|
||||
@@ -54,17 +83,19 @@ A useful query might be:
|
||||
```
|
||||
|
||||
You can get a summary of errors in a prior run by invoking commands like
|
||||
these:
|
||||
those shown below. Note that the testrunner.tcl script can be run directly
|
||||
on unix systems (including Macs) but you will need to add <tt>tclsh</tt>
|
||||
to the front on Windows.
|
||||
|
||||
```
|
||||
tclsh $(TESTDIR)/testrunner.tcl errors
|
||||
tclsh $(TESTDIR)/testrunner.tcl errors -v
|
||||
test/testrunner.tcl errors
|
||||
test/testrunner.tcl errors -v
|
||||
```
|
||||
|
||||
Running the command:
|
||||
|
||||
```
|
||||
tclsh $(TESTDIR)/testrunner.tcl status
|
||||
test/testrunner.tcl status
|
||||
```
|
||||
|
||||
in the directory containing the testrunner.db database runs various queries
|
||||
@@ -73,32 +104,43 @@ A good way to keep and eye on test progress is to run either of the two
|
||||
following commands:
|
||||
|
||||
```
|
||||
watch tclsh $(TESTDIR)/testrunner.tcl status
|
||||
tclsh $(TESTDIR)/testrunner.tcl status -d 2
|
||||
watch test/testrunner.tcl status
|
||||
test/testrunner.tcl status -d 2
|
||||
```
|
||||
|
||||
Both of the commands above accomplish about the same thing, but the second
|
||||
one has the advantage of not requiring "watch" to be installed on your
|
||||
system.
|
||||
|
||||
Sometimes testrunner.tcl uses the `testfixture` binary that it is run with
|
||||
to run tests (see "Binary Tests" below). Sometimes it builds testfixture and
|
||||
Sometimes testrunner.tcl uses the `testfixture` and `sqlite3` binaries that
|
||||
are in the directory from which testrunner.tcl is run.
|
||||
(see "Binary Tests" below). Sometimes it builds testfixture and
|
||||
other binaries in specific configurations to test (see "Source Tests").
|
||||
|
||||
<a name=help></a>
|
||||
## 1.4 Built-in help
|
||||
|
||||
Run this command:
|
||||
|
||||
```
|
||||
test/testrunner.tcl help
|
||||
```
|
||||
|
||||
To get a summary of all of the various command-line options available
|
||||
with testrunner.tcl
|
||||
|
||||
|
||||
<a name=binary_tests></a>
|
||||
# 2. Binary Tests
|
||||
|
||||
The commands described in this section all run various combinations of the Tcl
|
||||
test scripts using the `testfixture` binary used to run the testrunner.tcl
|
||||
script (i.e. they do not invoke the compiler to build new binaries, or the
|
||||
`make` command to run tests that are not Tcl scripts). The procedure to run
|
||||
these tests is therefore:
|
||||
|
||||
1. Build the "testfixture" (or "testfixture.exe" for windows) binary using
|
||||
whatever method seems convenient.
|
||||
|
||||
2. Test the binary built in step 1 by running testrunner.tcl with it,
|
||||
perhaps with various options.
|
||||
test scripts using whatever `testfixture` binary (and maybe also the `sqlite3`
|
||||
binary, depending on the test) that is found in the directory from which
|
||||
testrunner.tcl is launched. So typically, one must first run something
|
||||
like "`make testfixture sqlite3`" before launching binary tests. In other
|
||||
words, testrunner.tcl does not automatically build the binaries under test
|
||||
for binary tests. The testrunner.tcl expects the binaries to be available
|
||||
already.
|
||||
|
||||
The following sub-sections describe the various options that can be
|
||||
passed to testrunner.tcl to test binary testfixture builds.
|
||||
@@ -140,22 +182,22 @@ are defined in file *testrunner_data.tcl*.
|
||||
To run the "veryquick" test set, use either of the following:
|
||||
|
||||
```
|
||||
./testfixture $TESTDIR/testrunner.tcl
|
||||
./testfixture $TESTDIR/testrunner.tcl veryquick
|
||||
test/testrunner.tcl
|
||||
test/testrunner.tcl veryquick
|
||||
```
|
||||
|
||||
To run the "full" test suite:
|
||||
|
||||
```
|
||||
./testfixture $TESTDIR/testrunner.tcl full
|
||||
test/testrunner.tcl full
|
||||
```
|
||||
|
||||
To run the subset of the "full" test suite for which the test file name matches
|
||||
a specified pattern (e.g. all tests that start with "fts5"), either of:
|
||||
|
||||
```
|
||||
./testfixture $TESTDIR/testrunner.tcl fts5%
|
||||
./testfixture $TESTDIR/testrunner.tcl 'fts5*'
|
||||
test/testrunner.tcl fts5%
|
||||
test/testrunner.tcl 'fts5*'
|
||||
```
|
||||
|
||||
Strictly speaking, for a test to be run the pattern must match the script
|
||||
@@ -167,7 +209,7 @@ characters specified as part of the pattern are transformed to "\*".
|
||||
To run "all" tests (full + permutations):
|
||||
|
||||
```
|
||||
./testfixture $TESTDIR/testrunner.tcl all
|
||||
test/testrunner.tcl all
|
||||
```
|
||||
|
||||
<a name=binary_test_failures></a>
|
||||
@@ -186,10 +228,15 @@ If there is no permutation, the individual test script may be run with:
|
||||
Or, if the failure occured as part of a permutation:
|
||||
|
||||
```
|
||||
./testfixture $TESTDIR/testrunner.tcl $PERMUTATION $PATH_TO_SCRIPT
|
||||
test/testrunner.tcl $PERMUTATION $PATH_TO_SCRIPT
|
||||
```
|
||||
|
||||
TODO: An example instead of "$PERMUTATION" and $PATH\_TO\_SCRIPT?
|
||||
One can also rerun all tests that failed or did not complete
|
||||
in the previous invocation by typing:
|
||||
|
||||
```
|
||||
test/testrunner.tcl retest
|
||||
```
|
||||
|
||||
<a name=source_code_tests></a>
|
||||
# 3. Source Code Tests
|
||||
@@ -204,11 +251,9 @@ other tests. The advantages of this are that:
|
||||
* it ensures that tests are always run using binaries created with the
|
||||
same set of compiler options.
|
||||
|
||||
The testrunner.tcl commands described in this section may be run using
|
||||
either a *testfixture* (or testfixture.exe) build, or with any other Tcl
|
||||
shell that supports SQLite 3.31.1 or newer via "package require sqlite3".
|
||||
|
||||
TODO: ./configure + Makefile.msc build systems.
|
||||
The testrunner.tcl commands described in this section do not require that
|
||||
the testfixture and/or sqlite3 binaries be built ahead of time. Those
|
||||
binaries will be constructed automatically.
|
||||
|
||||
<a name=commands_to_run_tests></a>
|
||||
## 3.1. Commands to Run SQLite Tests
|
||||
@@ -218,7 +263,7 @@ the `make fuzztest` target once for each of two --enable-all builds - one
|
||||
with debugging enabled and one without:
|
||||
|
||||
```
|
||||
tclsh $TESTDIR/testrunner.tcl mdevtest
|
||||
test/testrunner.tcl mdevtest
|
||||
```
|
||||
|
||||
In other words, it is equivalent to running:
|
||||
@@ -227,13 +272,13 @@ In other words, it is equivalent to running:
|
||||
$TOP/configure --enable-all --enable-debug
|
||||
make fuzztest
|
||||
make testfixture
|
||||
./testfixture $TOP/test/testrunner.tcl veryquick
|
||||
$TOP/test/testrunner.tcl veryquick
|
||||
|
||||
# Then, after removing files created by the tests above:
|
||||
$TOP/configure --enable-all OPTS="-O0"
|
||||
make fuzztest
|
||||
make testfixture
|
||||
./testfixture $TOP/test/testrunner.tcl veryquick
|
||||
$TOP/test/testrunner.tcl veryquick
|
||||
```
|
||||
|
||||
The **sdevtest** command is identical to the mdevtest command, except that the
|
||||
@@ -241,7 +286,7 @@ second of the two builds is a sanitizer build. Specifically, this means that
|
||||
OPTS="-fsanitize=address,undefined" is specified instead of OPTS="-O0":
|
||||
|
||||
```
|
||||
tclsh $TESTDIR/testrunner.tcl sdevtest
|
||||
test/testrunner.tcl sdevtest
|
||||
```
|
||||
|
||||
The **release** command runs lots of tests under lots of builds. It runs
|
||||
@@ -250,7 +295,7 @@ on Linux, Windows or OSX. Refer to *testrunner\_data.tcl* for the details
|
||||
of the specific tests run.
|
||||
|
||||
```
|
||||
tclsh $TESTDIR/testrunner.tcl release
|
||||
test/testrunner.tcl release
|
||||
```
|
||||
|
||||
As with <a href=#source code tests>source code tests</a>, one or more patterns
|
||||
@@ -258,7 +303,7 @@ may be appended to any of the above commands (mdevtest, sdevtest or release).
|
||||
Pattern matching is used for both Tcl tests and fuzz tests.
|
||||
|
||||
```
|
||||
tclsh $TESTDIR/testrunner.tcl release rtree%
|
||||
test/testrunner.tcl release rtree%
|
||||
```
|
||||
|
||||
<a name=zipvfs_tests></a>
|
||||
@@ -268,14 +313,14 @@ testrunner.tcl can build a zipvfs-enabled testfixture and use it to run
|
||||
tests from the Zipvfs project with the following command:
|
||||
|
||||
```
|
||||
tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS
|
||||
test/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS
|
||||
```
|
||||
|
||||
This can be combined with any of "mdevtest", "sdevtest" or "release" to
|
||||
test both SQLite and Zipvfs with a single command:
|
||||
|
||||
```
|
||||
tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest
|
||||
test/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest
|
||||
```
|
||||
|
||||
<a name=source_code_test_failures></a>
|
||||
@@ -295,10 +340,10 @@ a dos \*.bat file on windows. For example:
|
||||
```
|
||||
# Create a script that recreates build configuration "Device-One" on
|
||||
# Linux or OSX:
|
||||
tclsh $TESTDIR/testrunner.tcl script Device-One > make.sh
|
||||
test/testrunner.tcl script Device-One > make.sh
|
||||
|
||||
# Create a script that recreates build configuration "Have-Not" on Windows:
|
||||
tclsh $TESTDIR/testrunner.tcl script Have-Not > make.bat
|
||||
test/testrunner.tcl script Have-Not > make.bat
|
||||
```
|
||||
|
||||
The generated bash or \*.bat file script accepts a single argument - a makefile
|
||||
@@ -321,7 +366,7 @@ Thus, for example, to run a full releasetest including an external
|
||||
dbsqlfuzz database, run a command like one of these:
|
||||
|
||||
```
|
||||
tclsh test/testrunner.tcl releasetest --fuzzdb ../fuzz/20250415.db
|
||||
test/testrunner.tcl releasetest --fuzzdb ../fuzz/20250415.db
|
||||
FUZZDB=../fuzz/20250415.db make releasetest
|
||||
nmake /f Makefile.msc FUZZDB=../fuzz/20250415.db releasetest
|
||||
```
|
||||
@@ -331,7 +376,7 @@ databases. So if you want to run *only* tests involving the external
|
||||
database, you can use a command something like this:
|
||||
|
||||
```
|
||||
tclsh test/testrunner.tcl releasetest 20250415 --fuzzdb ../fuzz/20250415.db
|
||||
test/testrunner.tcl releasetest 20250415 --fuzzdb ../fuzz/20250415.db
|
||||
```
|
||||
|
||||
<a name=testrunner_options></a>
|
||||
@@ -345,7 +390,7 @@ required by a test, not to run any actual tests. For example:
|
||||
|
||||
```
|
||||
# Build binaries required by release test.
|
||||
tclsh $TESTDIR/testrunner.tcl --buildonly release"
|
||||
test/testrunner.tcl --buildonly release"
|
||||
```
|
||||
|
||||
The **--dryrun** option prevents testrunner.tcl from building any binaries
|
||||
@@ -354,7 +399,7 @@ would normally execute into the testrunner.log file. Example:
|
||||
|
||||
```
|
||||
# Log the shell commmands that make up the mdevtest test.
|
||||
tclsh $TESTDIR/testrunner.tcl --dryrun mdevtest"
|
||||
test/testrunner.tcl --dryrun mdevtest"
|
||||
```
|
||||
|
||||
The **--explain** option is similar to --dryrun in that it prevents
|
||||
@@ -364,7 +409,7 @@ summary of all the builds and tests that would have been run.
|
||||
|
||||
```
|
||||
# Show what builds and tests would have been run
|
||||
tclsh $TESTDIR/testrunner.tcl --explain mdevtest
|
||||
test/testrunner.tcl --explain mdevtest
|
||||
```
|
||||
|
||||
The **--status** option uses VT100 escape sequences to display the test
|
||||
@@ -380,7 +425,7 @@ When running either binary or source code tests, testrunner.tcl reports the
|
||||
number of jobs it intends to use to stdout. e.g.
|
||||
|
||||
```
|
||||
$ ./testfixture $TESTDIR/testrunner.tcl
|
||||
$ test/testrunner.tcl
|
||||
splitting work across 16 jobs
|
||||
... more output ...
|
||||
```
|
||||
@@ -390,7 +435,7 @@ of real cores on the machine. This can be overridden using the "--jobs" (or -j)
|
||||
switch:
|
||||
|
||||
```
|
||||
$ ./testfixture $TESTDIR/testrunner.tcl --jobs 8
|
||||
$ test/testrunner.tcl --jobs 8
|
||||
splitting work across 8 jobs
|
||||
... more output ...
|
||||
```
|
||||
@@ -400,5 +445,5 @@ running by exucuting the following command from the directory containing the
|
||||
testrunner.log and testrunner.db files:
|
||||
|
||||
```
|
||||
$ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS
|
||||
$ test/testrunner.tcl njob $NEW_NUMBER_OF_JOBS
|
||||
```
|
||||
|
||||
+3
-6
@@ -1816,9 +1816,7 @@ static int fts3CursorSeekStmt(Fts3Cursor *pCsr){
|
||||
zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist);
|
||||
if( !zSql ) return SQLITE_NOMEM;
|
||||
p->bLock++;
|
||||
rc = sqlite3_prepare_v3(
|
||||
p->db, zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0
|
||||
);
|
||||
rc = sqlite3Fts3PrepareStmt(p, zSql, 1, 1, &pCsr->pStmt);
|
||||
p->bLock--;
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
@@ -3393,9 +3391,7 @@ static int fts3FilterMethod(
|
||||
}
|
||||
if( zSql ){
|
||||
p->bLock++;
|
||||
rc = sqlite3_prepare_v3(
|
||||
p->db,zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0
|
||||
);
|
||||
rc = sqlite3Fts3PrepareStmt(p, zSql, 1, 1, &pCsr->pStmt);
|
||||
p->bLock--;
|
||||
sqlite3_free(zSql);
|
||||
}else{
|
||||
@@ -4018,6 +4014,7 @@ static int fts3IntegrityMethod(
|
||||
|
||||
UNUSED_PARAMETER(isQuick);
|
||||
rc = sqlite3Fts3IntegrityCheck(p, &bOk);
|
||||
assert( pVtab->zErrMsg==0 || rc!=SQLITE_OK );
|
||||
assert( rc!=SQLITE_CORRUPT_VTAB );
|
||||
if( rc==SQLITE_ERROR || (rc&0xFF)==SQLITE_CORRUPT ){
|
||||
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
|
||||
|
||||
+19
-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
|
||||
@@ -601,6 +610,15 @@ int sqlite3Fts3Incrmerge(Fts3Table*,int,int);
|
||||
(*(u8*)(p)&0x80) ? sqlite3Fts3GetVarint32(p, piVal) : (*piVal=*(u8*)(p), 1) \
|
||||
)
|
||||
|
||||
int sqlite3Fts3PrepareStmt(
|
||||
Fts3Table *p, /* Prepare for this connection */
|
||||
const char *zSql, /* SQL to prepare */
|
||||
int bPersist, /* True to set SQLITE_PREPARE_PERSISTENT */
|
||||
int bAllowVtab, /* True to omit SQLITE_PREPARE_NO_VTAB */
|
||||
sqlite3_stmt **pp /* OUT: Prepared statement */
|
||||
);
|
||||
|
||||
|
||||
/* fts3.c */
|
||||
void sqlite3Fts3ErrMsg(char**,const char*,...);
|
||||
int sqlite3Fts3PutVarint(char *, sqlite3_int64);
|
||||
|
||||
+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. */
|
||||
|
||||
+23
-5
@@ -273,6 +273,24 @@ struct SegmentNode {
|
||||
#define SQL_UPDATE_LEVEL_IDX 38
|
||||
#define SQL_UPDATE_LEVEL 39
|
||||
|
||||
/*
|
||||
** Wrapper around sqlite3_prepare_v3() to ensure that SQLITE_PREPARE_FROM_DDL
|
||||
** is always set.
|
||||
*/
|
||||
int sqlite3Fts3PrepareStmt(
|
||||
Fts3Table *p, /* Prepare for this connection */
|
||||
const char *zSql, /* SQL to prepare */
|
||||
int bPersist, /* True to set SQLITE_PREPARE_PERSISTENT */
|
||||
int bAllowVtab, /* True to omit SQLITE_PREPARE_NO_VTAB */
|
||||
sqlite3_stmt **pp /* OUT: Prepared statement */
|
||||
){
|
||||
int f = SQLITE_PREPARE_FROM_DDL
|
||||
|((bAllowVtab==0) ? SQLITE_PREPARE_NO_VTAB : 0)
|
||||
|(bPersist ? SQLITE_PREPARE_PERSISTENT : 0);
|
||||
|
||||
return sqlite3_prepare_v3(p->db, zSql, -1, f, pp, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is used to obtain an SQLite prepared statement handle
|
||||
** for the statement identified by the second argument. If successful,
|
||||
@@ -398,12 +416,12 @@ static int fts3SqlStmt(
|
||||
|
||||
pStmt = p->aStmt[eStmt];
|
||||
if( !pStmt ){
|
||||
int f = SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB;
|
||||
int bAllowVtab = 0;
|
||||
char *zSql;
|
||||
if( eStmt==SQL_CONTENT_INSERT ){
|
||||
zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist);
|
||||
}else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){
|
||||
f &= ~SQLITE_PREPARE_NO_VTAB;
|
||||
bAllowVtab = 1;
|
||||
zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist);
|
||||
}else{
|
||||
zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName);
|
||||
@@ -411,7 +429,7 @@ static int fts3SqlStmt(
|
||||
if( !zSql ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = sqlite3_prepare_v3(p->db, zSql, -1, f, &pStmt, NULL);
|
||||
rc = sqlite3Fts3PrepareStmt(p, zSql, 1, bAllowVtab, &pStmt);
|
||||
sqlite3_free(zSql);
|
||||
assert( rc==SQLITE_OK || pStmt==0 );
|
||||
p->aStmt[eStmt] = pStmt;
|
||||
@@ -3578,7 +3596,7 @@ static int fts3DoRebuild(Fts3Table *p){
|
||||
if( !zSql ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
|
||||
rc = sqlite3Fts3PrepareStmt(p, zSql, 0, 1, &pStmt);
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
|
||||
@@ -5331,7 +5349,7 @@ int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){
|
||||
if( !zSql ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
|
||||
rc = sqlite3Fts3PrepareStmt(p, zSql, 0, 1, &pStmt);
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -81,7 +81,13 @@ typedef sqlite3_uint64 u64;
|
||||
# define FLEXARRAY 1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif /* SQLITE_AMALGAMATION */
|
||||
|
||||
/*
|
||||
** Constants for the largest and smallest possible 32-bit signed integers.
|
||||
*/
|
||||
# define LARGEST_INT32 ((int)(0x7fffffff))
|
||||
# define SMALLEST_INT32 ((int)((-1) - LARGEST_INT32))
|
||||
|
||||
/* Truncate very long tokens to this many bytes. Hard limit is
|
||||
** (65536-1-1-4-9)==65521 bytes. The limiting factor is the 16-bit offset
|
||||
|
||||
+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{
|
||||
|
||||
+41
-35
@@ -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;
|
||||
@@ -5240,7 +5240,7 @@ static void fts5DoSecureDelete(
|
||||
int iSegid = pSeg->pSeg->iSegid;
|
||||
u8 *aPg = pSeg->pLeaf->p;
|
||||
int nPg = pSeg->pLeaf->nn;
|
||||
int iPgIdx = pSeg->pLeaf->szLeaf;
|
||||
int iPgIdx = pSeg->pLeaf->szLeaf; /* Offset of page footer */
|
||||
|
||||
u64 iDelta = 0;
|
||||
int iNextOff = 0;
|
||||
@@ -5319,7 +5319,7 @@ static void fts5DoSecureDelete(
|
||||
iSOP += fts5GetVarint32(&aPg[iSOP], nPos);
|
||||
}
|
||||
assert_nc( iSOP==pSeg->iLeafOffset );
|
||||
iNextOff = pSeg->iLeafOffset + pSeg->nPos;
|
||||
iNextOff = iSOP + pSeg->nPos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5399,31 +5399,31 @@ static void fts5DoSecureDelete(
|
||||
** is another term following it on this page. So the subsequent term
|
||||
** needs to be moved to replace the term associated with the entry
|
||||
** being removed. */
|
||||
int nPrefix = 0;
|
||||
int nSuffix = 0;
|
||||
int nPrefix2 = 0;
|
||||
int nSuffix2 = 0;
|
||||
u64 nPrefix = 0;
|
||||
u64 nSuffix = 0;
|
||||
u64 nPrefix2 = 0;
|
||||
u64 nSuffix2 = 0;
|
||||
|
||||
iDelKeyOff = iNextOff;
|
||||
iNextOff += fts5GetVarint32(&aPg[iNextOff], nPrefix2);
|
||||
iNextOff += fts5GetVarint32(&aPg[iNextOff], nSuffix2);
|
||||
iNextOff += fts5GetVarint(&aPg[iNextOff], &nPrefix2);
|
||||
iNextOff += fts5GetVarint(&aPg[iNextOff], &nSuffix2);
|
||||
|
||||
if( iKey!=1 ){
|
||||
iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nPrefix);
|
||||
iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nPrefix);
|
||||
}
|
||||
iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nSuffix);
|
||||
iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nSuffix);
|
||||
|
||||
nPrefix = MIN(nPrefix, nPrefix2);
|
||||
nSuffix = (nPrefix2 + nSuffix2) - nPrefix;
|
||||
|
||||
if( (iKeyOff+nSuffix)>iPgIdx || (iNextOff+nSuffix2)>iPgIdx ){
|
||||
if( (iKeyOff+nSuffix)>(u64)iPgIdx || (iNextOff+nSuffix2)>(u64)iPgIdx ){
|
||||
FTS5_CORRUPT_IDX(p);
|
||||
}else{
|
||||
if( iKey!=1 ){
|
||||
iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix);
|
||||
}
|
||||
iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix);
|
||||
if( nPrefix2>pSeg->term.n ){
|
||||
if( nPrefix2>(u64)pSeg->term.n ){
|
||||
FTS5_CORRUPT_IDX(p);
|
||||
}else if( nPrefix2>nPrefix ){
|
||||
memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix);
|
||||
@@ -5454,7 +5454,7 @@ static void fts5DoSecureDelete(
|
||||
u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
|
||||
int nTermIdx = pTerm->nn - pTerm->szLeaf;
|
||||
int iTermIdx = 0;
|
||||
int iTermOff = 0;
|
||||
i64 iTermOff = 0;
|
||||
|
||||
while( 1 ){
|
||||
u32 iVal = 0;
|
||||
@@ -5465,12 +5465,15 @@ static void fts5DoSecureDelete(
|
||||
}
|
||||
nTermIdx = iTermIdx;
|
||||
|
||||
memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
|
||||
fts5PutU16(&pTerm->p[2], iTermOff);
|
||||
|
||||
fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
|
||||
if( nTermIdx==0 ){
|
||||
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
|
||||
if( iTermOff>pTerm->szLeaf ){
|
||||
FTS5_CORRUPT_IDX(p);
|
||||
}else{
|
||||
memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
|
||||
fts5PutU16(&pTerm->p[2], iTermOff);
|
||||
fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
|
||||
if( nTermIdx==0 ){
|
||||
fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
|
||||
}
|
||||
}
|
||||
}
|
||||
fts5DataRelease(pTerm);
|
||||
@@ -5493,7 +5496,9 @@ static void fts5DoSecureDelete(
|
||||
int iPrevKeyOut = 0;
|
||||
int iKeyIn = 0;
|
||||
|
||||
memmove(&aPg[iOff], &aPg[iNextOff], nMove);
|
||||
if( nMove>0 ){
|
||||
memmove(&aPg[iOff], &aPg[iNextOff], nMove);
|
||||
}
|
||||
iPgIdx -= nShift;
|
||||
nPg = iPgIdx;
|
||||
fts5PutU16(&aPg[2], iPgIdx);
|
||||
@@ -5931,7 +5936,7 @@ int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
|
||||
fts5StructureRelease(pStruct);
|
||||
pStruct = pNew;
|
||||
nMin = 1;
|
||||
nMerge = nMerge*-1;
|
||||
nMerge = (nMerge==SMALLEST_INT32 ? LARGEST_INT32 : (nMerge*-1));
|
||||
}
|
||||
if( pStruct && pStruct->nLevel ){
|
||||
if( fts5IndexMerge(p, &pStruct, nMerge, nMin) ){
|
||||
@@ -6413,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];
|
||||
@@ -6478,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;
|
||||
@@ -6508,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 ){
|
||||
@@ -7042,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;
|
||||
@@ -7141,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 ){
|
||||
|
||||
+48
-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;
|
||||
@@ -2081,6 +2106,7 @@ static int fts5UpdateMethod(
|
||||
}
|
||||
|
||||
update_out:
|
||||
sqlite3Fts5IndexCloseReader(pTab->p.pIndex);
|
||||
pTab->p.pConfig->pzErrmsg = 0;
|
||||
return rc;
|
||||
}
|
||||
@@ -3762,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{
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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}
|
||||
|
||||
@@ -64,4 +64,21 @@ foreach {tn sql} {
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Verify that https://sqlite.org/forum/forumpost/95413eb410 has been
|
||||
# fixed.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE f1 USING fts5(x);
|
||||
BEGIN TRANSACTION;
|
||||
INSERT INTO f1(x) VALUES('abc def ghi');
|
||||
}
|
||||
do_test 2.1 {
|
||||
sqlite3_interrupt db
|
||||
} {}
|
||||
do_execsql_test 2.2 {
|
||||
ROLLBACK
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -238,6 +238,22 @@ do_execsql_test 6.3 {
|
||||
INSERT INTO g1(g1) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Check that passing -2147483648 as the parameter to a merge command
|
||||
# does not cause a signed integer overflow error.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 7.0 {
|
||||
CREATE VIRTUAL TABLE f1 USING fts5(a);
|
||||
}
|
||||
do_execsql_test 7.1 {
|
||||
INSERT INTO f1 VALUES('one two three');
|
||||
INSERT INTO f1 VALUES('four five six');
|
||||
INSERT INTO f1 VALUES('seven eight nine');
|
||||
}
|
||||
do_execsql_test 7.2 {
|
||||
INSERT INTO f1(f1, rank) VALUES('merge', -2147483648);
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+7
-7
@@ -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[]){
|
||||
@@ -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:
|
||||
|
||||
@@ -306,6 +306,10 @@ static int binfoCompute(sqlite3 *db, int pgno, BinfoCursor *pCsr){
|
||||
nEntry *= (nCell+1);
|
||||
if( aData[0]==10 || aData[0]==13 ) break;
|
||||
nPage *= (nCell+1);
|
||||
if( 14+2*(nCell/2)>=pgsz ){
|
||||
rc = SQLITE_CORRUPT;
|
||||
break;
|
||||
}
|
||||
if( nCell<=1 ){
|
||||
pgno = get_uint32(aData+8);
|
||||
}else{
|
||||
@@ -339,7 +343,7 @@ static int binfoColumn(
|
||||
sqlite3 *db = sqlite3_context_db_handle(ctx);
|
||||
int rc = binfoCompute(db, pgno, pCsr);
|
||||
if( rc ){
|
||||
pCursor->pVtab->zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
|
||||
pCursor->pVtab->zErrMsg = sqlite3_mprintf("%s", sqlite3_errstr(rc));
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
+25
-13
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -59,7 +59,7 @@ static void compressFunc(
|
||||
pIn = sqlite3_value_blob(argv[0]);
|
||||
nIn = sqlite3_value_bytes(argv[0]);
|
||||
nOut = 13 + nIn + (nIn+999)/1000;
|
||||
pOut = sqlite3_malloc( nOut+5 );
|
||||
pOut = sqlite3_malloc64( nOut+5 );
|
||||
for(i=4; i>=0; i--){
|
||||
x[i] = (nIn >> (7*(4-i)))&0x7f;
|
||||
}
|
||||
@@ -98,7 +98,7 @@ static void uncompressFunc(
|
||||
nOut = (nOut<<7) | (pIn[i]&0x7f);
|
||||
if( (pIn[i]&0x80)!=0 ){ i++; break; }
|
||||
}
|
||||
pOut = sqlite3_malloc( nOut+1 );
|
||||
pOut = sqlite3_malloc64( nOut+1 );
|
||||
rc = uncompress(pOut, &nOut, &pIn[i], nIn-i);
|
||||
if( rc==Z_OK ){
|
||||
sqlite3_result_blob(context, pOut, nOut, sqlite3_free);
|
||||
|
||||
+16
-13
@@ -24,8 +24,8 @@
|
||||
** schema= parameter, like this:
|
||||
**
|
||||
** CREATE VIRTUAL TABLE temp.csv2 USING csv(
|
||||
** filename = "../http.log",
|
||||
** schema = "CREATE TABLE x(date,ipaddr,url,referrer,userAgent)"
|
||||
** filename = '../http.log',
|
||||
** schema = 'CREATE TABLE x(date,ipaddr,url,referrer,userAgent)'
|
||||
** );
|
||||
**
|
||||
** Instead of specifying a file, the text of the CSV can be loaded using
|
||||
@@ -62,6 +62,10 @@ SQLITE_EXTENSION_INIT1
|
||||
# define CSV_NOINLINE
|
||||
#endif
|
||||
|
||||
#ifndef SQLITEINT_H
|
||||
typedef sqlite3_int64 i64;
|
||||
typedef sqlite3_uint64 u64;
|
||||
#endif
|
||||
|
||||
/* Max size of the error message in a CsvReader */
|
||||
#define CSV_MXERR 200
|
||||
@@ -74,9 +78,9 @@ typedef struct CsvReader CsvReader;
|
||||
struct CsvReader {
|
||||
FILE *in; /* Read the CSV text from this input stream */
|
||||
char *z; /* Accumulated text for a field */
|
||||
int n; /* Number of bytes in z */
|
||||
int nAlloc; /* Space allocated for z[] */
|
||||
int nLine; /* Current line number */
|
||||
i64 n; /* Number of bytes in z */
|
||||
i64 nAlloc; /* Space allocated for z[] */
|
||||
i64 nLine; /* Current line number */
|
||||
int bNotFirst; /* True if prior text has been seen */
|
||||
int cTerm; /* Character that terminated the most recent field */
|
||||
size_t iIn; /* Next unread character in the input buffer */
|
||||
@@ -174,7 +178,7 @@ static int csv_getc(CsvReader *p){
|
||||
** Return 0 on success and non-zero if there is an OOM error */
|
||||
static CSV_NOINLINE int csv_resize_and_append(CsvReader *p, char c){
|
||||
char *zNew;
|
||||
int nNew = p->nAlloc*2 + 100;
|
||||
i64 nNew = p->nAlloc*2 + 100;
|
||||
zNew = sqlite3_realloc64(p->z, nNew);
|
||||
if( zNew ){
|
||||
p->z = zNew;
|
||||
@@ -218,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);
|
||||
@@ -322,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;
|
||||
|
||||
@@ -494,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
|
||||
@@ -510,11 +514,10 @@ static int csvtabConnect(
|
||||
# define CSV_DATA (azPValue[1])
|
||||
# define CSV_SCHEMA (azPValue[2])
|
||||
|
||||
|
||||
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++){
|
||||
@@ -707,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);
|
||||
|
||||
+63
-13
@@ -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,6 +73,7 @@ static Decimal *decimalNewFromText(const char *zIn, int n){
|
||||
int i;
|
||||
int iExp = 0;
|
||||
|
||||
if( zIn==0 ) goto new_from_text_failed;
|
||||
p = sqlite3_malloc( sizeof(*p) );
|
||||
if( p==0 ) goto new_from_text_failed;
|
||||
p->sign = 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);
|
||||
}
|
||||
@@ -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 },
|
||||
|
||||
+173
-62
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -38,9 +38,11 @@ SQLITE_EXTENSION_INIT1
|
||||
|
||||
#ifndef SQLITE_AMALGAMATION
|
||||
/*
|
||||
** The "u32" type must be an unsigned 32-bit integer. Adjust this
|
||||
** The "u32" type must be an unsigned 32-bit integer. "u64" is
|
||||
** an unsigned 64-bit integer.
|
||||
*/
|
||||
typedef unsigned int u32;
|
||||
typedef sqlite3_uint64 u64;
|
||||
|
||||
/*
|
||||
** Must be a 16-bit value
|
||||
@@ -541,8 +543,8 @@ static int delta_apply(
|
||||
int lenDelta, /* Length of the delta */
|
||||
char *zOut /* Write the output into this preallocated buffer */
|
||||
){
|
||||
unsigned int limit;
|
||||
unsigned int total = 0;
|
||||
sqlite3_uint64 limit;
|
||||
sqlite3_uint64 total = 0;
|
||||
#ifdef FOSSIL_ENABLE_DELTA_CKSUM_TEST
|
||||
char *zOrigOut = zOut;
|
||||
#endif
|
||||
@@ -570,7 +572,7 @@ static int delta_apply(
|
||||
/* ERROR: copy exceeds output file size */
|
||||
return -1;
|
||||
}
|
||||
if( ofst+cnt > lenSrc ){
|
||||
if( (u64)ofst+(u64)cnt > (u64)lenSrc ){
|
||||
/* ERROR: copy extends past end of input */
|
||||
return -1;
|
||||
}
|
||||
|
||||
+1
-1
@@ -617,7 +617,7 @@ 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;
|
||||
|
||||
+35
-1
@@ -179,9 +179,9 @@ static void ieee754func(
|
||||
}
|
||||
|
||||
if( m<0 ){
|
||||
if( m<(-9223372036854775807LL) ) return;
|
||||
isNeg = 1;
|
||||
m = -m;
|
||||
if( m<0 ) return;
|
||||
}else if( m==0 && e>-1000 && e<1000 ){
|
||||
sqlite3_result_double(context, 0.0);
|
||||
return;
|
||||
@@ -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;
|
||||
|
||||
+22
-8
@@ -32,7 +32,7 @@
|
||||
** ^X X occurring at the beginning of the string
|
||||
** X$ X occurring at the end of the string
|
||||
** . Match any single character
|
||||
** \c Character c where c is one of \{}()[]|*+?.
|
||||
** \c Character c where c is one of \{}()[]|*+?-.
|
||||
** \c C-language escapes for c in afnrtv. ex: \t or \n
|
||||
** \uXXXX Where XXXX is exactly 4 hex digits, unicode value XXXX
|
||||
** \xXX Where XX is exactly 2 hex digits, unicode value XX
|
||||
@@ -417,7 +417,7 @@ static int re_hex(int c, int *pV){
|
||||
** return its interpretation.
|
||||
*/
|
||||
static unsigned re_esc_char(ReCompiled *p){
|
||||
static const char zEsc[] = "afnrtv\\()*.+?[$^{|}]";
|
||||
static const char zEsc[] = "afnrtv\\()*.+?[$^{|}]-";
|
||||
static const char zTrans[] = "\a\f\n\r\t\v";
|
||||
int i, v = 0;
|
||||
char c;
|
||||
@@ -740,11 +740,18 @@ static const char *re_compile(
|
||||
}
|
||||
|
||||
/*
|
||||
** Compute a reasonable limit on the length of the REGEXP NFA.
|
||||
** The value of LIMIT_MAX_PATTERN_LENGTH.
|
||||
*/
|
||||
static int re_maxlen(sqlite3_context *context){
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
return 75 + sqlite3_limit(db, SQLITE_LIMIT_LIKE_PATTERN_LENGTH,-1)/2;
|
||||
return sqlite3_limit(db, SQLITE_LIMIT_LIKE_PATTERN_LENGTH,-1);
|
||||
}
|
||||
|
||||
/*
|
||||
** Maximum NFA size given a maximum pattern length.
|
||||
*/
|
||||
static int re_maxnfa(int mxlen){
|
||||
return 75+mxlen/2;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -770,10 +777,17 @@ static void re_sql_func(
|
||||
(void)argc; /* Unused */
|
||||
pRe = sqlite3_get_auxdata(context, 0);
|
||||
if( pRe==0 ){
|
||||
int mxLen = re_maxlen(context);
|
||||
int nPattern;
|
||||
zPattern = (const char*)sqlite3_value_text(argv[0]);
|
||||
if( zPattern==0 ) return;
|
||||
zErr = re_compile(&pRe, zPattern, re_maxlen(context),
|
||||
sqlite3_user_data(context)!=0);
|
||||
nPattern = sqlite3_value_bytes(argv[0]);
|
||||
if( nPattern>mxLen ){
|
||||
zErr = "REGEXP pattern too big";
|
||||
}else{
|
||||
zErr = re_compile(&pRe, zPattern, re_maxnfa(mxLen),
|
||||
sqlite3_user_data(context)!=0);
|
||||
}
|
||||
if( zErr ){
|
||||
re_free(pRe);
|
||||
sqlite3_result_error(context, zErr, -1);
|
||||
@@ -814,7 +828,6 @@ static void re_bytecode_func(
|
||||
int i;
|
||||
int n;
|
||||
char *z;
|
||||
(void)argc;
|
||||
static const char *ReOpName[] = {
|
||||
"EOF",
|
||||
"MATCH",
|
||||
@@ -837,9 +850,10 @@ 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_maxlen(context),
|
||||
zErr = re_compile(&pRe, zPattern, re_maxnfa(re_maxlen(context)),
|
||||
sqlite3_user_data(context)!=0);
|
||||
if( zErr ){
|
||||
re_free(pRe);
|
||||
|
||||
+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);
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ 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]);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+48
-6
@@ -14,6 +14,8 @@
|
||||
** on stdout when its key interfaces are called. This is intended for
|
||||
** interactive analysis and debugging of virtual table interfaces.
|
||||
**
|
||||
** HOW TO COMPILE:
|
||||
**
|
||||
** To build this extension as a separately loaded shared library or
|
||||
** DLL, use compiler command-lines similar to the following:
|
||||
**
|
||||
@@ -21,7 +23,7 @@
|
||||
** (mac) clang -fPIC -dynamiclib vtablog.c -o vtablog.dylib
|
||||
** (windows) cl vtablog.c -link -dll -out:vtablog.dll
|
||||
**
|
||||
** Usage example:
|
||||
** USAGE EXAMPLE:
|
||||
**
|
||||
** .load ./vtablog
|
||||
** CREATE VIRTUAL TABLE temp.log USING vtablog(
|
||||
@@ -29,6 +31,23 @@
|
||||
** rows=25
|
||||
** );
|
||||
** SELECT * FROM log;
|
||||
**
|
||||
** ARGUMENTS TO CREATE VIRTUAL TABLE:
|
||||
**
|
||||
** In "CREATE VIRTUAL TABLE temp.log AS vtablog(ARGS....)" statement, the
|
||||
** ARGS argument is a list of key-value pairs that can be any of the
|
||||
** following.
|
||||
**
|
||||
** schema=TEXT Text is a CREATE TABLE statement that defines
|
||||
** the schema of the new virtual table.
|
||||
**
|
||||
** rows=N The table as N rows.
|
||||
**
|
||||
** consume_order_by=N If the left-most ORDER BY terms is ASC and
|
||||
** against column N (where the leftmost column
|
||||
** is #1) then set the orderByConsumed=1 flag in
|
||||
** xBestIndex. Or if the left-most ORDER BY is
|
||||
** DESC and against column -N, do likewise.
|
||||
*/
|
||||
#include "sqlite3ext.h"
|
||||
SQLITE_EXTENSION_INIT1
|
||||
@@ -49,6 +68,8 @@ struct vtablog_vtab {
|
||||
char *zName; /* Table name. argv[2] of xConnect/xCreate */
|
||||
int nRow; /* Number of rows in the table */
|
||||
int nCursor; /* Number of cursors created */
|
||||
int iConsumeOB; /* Consume the ORDER BY clause if on column N-th
|
||||
** and consumeOB=N or consumeOB=(-N) and DESC */
|
||||
};
|
||||
|
||||
/* vtablog_cursor is a subclass of sqlite3_vtab_cursor which will
|
||||
@@ -180,6 +201,7 @@ static int vtablogConnectCreate(
|
||||
int rc;
|
||||
char *zSchema = 0;
|
||||
char *zNRow = 0;
|
||||
char *zConsumeOB = 0;
|
||||
|
||||
printf("%s.%s.%s():\n", argv[1], argv[2],
|
||||
isCreate ? "xCreate" : "xConnect");
|
||||
@@ -203,6 +225,10 @@ static int vtablogConnectCreate(
|
||||
rc = SQLITE_ERROR;
|
||||
goto vtablog_end_connect;
|
||||
}
|
||||
if( vtablog_string_parameter(pzErr, "consume_order_by", z, &zConsumeOB) ){
|
||||
rc = SQLITE_ERROR;
|
||||
goto vtablog_end_connect;
|
||||
}
|
||||
}
|
||||
if( zSchema==0 ){
|
||||
zSchema = sqlite3_mprintf("%s","CREATE TABLE x(a,b);");
|
||||
@@ -221,6 +247,10 @@ static int vtablogConnectCreate(
|
||||
pNew->nRow = 10;
|
||||
if( zNRow ) pNew->nRow = atoi(zNRow);
|
||||
printf(" nrow = %d\n", pNew->nRow);
|
||||
if( zConsumeOB ) pNew->iConsumeOB = atoi(zConsumeOB);
|
||||
if( pNew->iConsumeOB ){
|
||||
printf(" consume_order_by = %d\n", pNew->iConsumeOB);
|
||||
}
|
||||
pNew->zDb = sqlite3_mprintf("%s", argv[1]);
|
||||
pNew->zName = sqlite3_mprintf("%s", argv[2]);
|
||||
}
|
||||
@@ -228,6 +258,7 @@ static int vtablogConnectCreate(
|
||||
vtablog_end_connect:
|
||||
sqlite3_free(zSchema);
|
||||
sqlite3_free(zNRow);
|
||||
sqlite3_free(zConsumeOB);
|
||||
return rc;
|
||||
}
|
||||
static int vtablogCreate(
|
||||
@@ -514,16 +545,27 @@ static int vtablogBestIndex(
|
||||
}
|
||||
}
|
||||
printf(" nOrderBy: %d\n", p->nOrderBy);
|
||||
for(i=0; i<p->nOrderBy; i++){
|
||||
printf(" orderby[%d]: col=%d desc=%d\n",
|
||||
i,
|
||||
p->aOrderBy[i].iColumn,
|
||||
p->aOrderBy[i].desc);
|
||||
if( p->nOrderBy ){
|
||||
for(i=0; i<p->nOrderBy; i++){
|
||||
printf(" orderby[%d]: col=%d desc=%d\n",
|
||||
i,
|
||||
p->aOrderBy[i].iColumn,
|
||||
p->aOrderBy[i].desc);
|
||||
}
|
||||
if( pTab->iConsumeOB ){
|
||||
int N = p->aOrderBy[0].iColumn+1;
|
||||
if( (p->aOrderBy[0].desc && N==-pTab->iConsumeOB)
|
||||
|| (!p->aOrderBy[0].desc && N==pTab->iConsumeOB)
|
||||
){
|
||||
p->orderByConsumed = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
p->estimatedCost = (double)500;
|
||||
p->estimatedRows = 500;
|
||||
printf(" idxNum=%d\n", p->idxNum);
|
||||
printf(" idxStr=NULL\n");
|
||||
printf(" sqlite3_vtab_distinct()=%d\n", sqlite3_vtab_distinct(p));
|
||||
printf(" orderByConsumed=%d\n", p->orderByConsumed);
|
||||
printf(" estimatedCost=%g\n", p->estimatedCost);
|
||||
printf(" estimatedRows=%lld\n", p->estimatedRows);
|
||||
|
||||
+32
-27
@@ -393,7 +393,7 @@ static int zipfileConnect(
|
||||
|
||||
rc = sqlite3_declare_vtab(db, ZIPFILE_SCHEMA);
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = (ZipfileTab*)sqlite3_malloc64((sqlite3_int64)nByte+nFile);
|
||||
pNew = (ZipfileTab*)sqlite3_malloc64((i64)nByte+nFile);
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, nByte+nFile);
|
||||
pNew->db = db;
|
||||
@@ -539,14 +539,15 @@ static void zipfileCursorErr(ZipfileCsr *pCsr, const char *zFmt, ...){
|
||||
static int zipfileReadData(
|
||||
FILE *pFile, /* Read from this file */
|
||||
u8 *aRead, /* Read into this buffer */
|
||||
int nRead, /* Number of bytes to read */
|
||||
i64 nRead, /* Number of bytes to read */
|
||||
i64 iOff, /* Offset to read from */
|
||||
char **pzErrmsg /* OUT: Error message (from sqlite3_malloc) */
|
||||
){
|
||||
size_t n;
|
||||
fseek(pFile, (long)iOff, SEEK_SET);
|
||||
n = fread(aRead, 1, nRead, pFile);
|
||||
if( (int)n!=nRead ){
|
||||
n = fread(aRead, 1, (long)nRead, pFile);
|
||||
if( n!=(size_t)nRead ){
|
||||
sqlite3_free(*pzErrmsg);
|
||||
*pzErrmsg = sqlite3_mprintf("error in fread()");
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
@@ -563,7 +564,7 @@ static int zipfileAppendData(
|
||||
fseek(pTab->pWriteFd, (long)pTab->szCurrent, SEEK_SET);
|
||||
n = fwrite(aWrite, 1, nWrite, pTab->pWriteFd);
|
||||
if( (int)n!=nWrite ){
|
||||
pTab->base.zErrMsg = sqlite3_mprintf("error in fwrite()");
|
||||
zipfileTableErr(pTab,"error in fwrite()");
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pTab->szCurrent += nWrite;
|
||||
@@ -704,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);
|
||||
|
||||
@@ -810,6 +816,7 @@ static void zipfileMtimeToDos(ZipfileCDS *pCds, u32 mUnixTime){
|
||||
** generic corruption message and return SQLITE_CORRUPT;
|
||||
*/
|
||||
static int zipfileCorrupt(char **pzErr){
|
||||
sqlite3_free(*pzErr);
|
||||
*pzErr = sqlite3_mprintf("zip archive is corrupt");
|
||||
return SQLITE_CORRUPT;
|
||||
}
|
||||
@@ -828,7 +835,7 @@ static int zipfileCorrupt(char **pzErr){
|
||||
static int zipfileGetEntry(
|
||||
ZipfileTab *pTab, /* Store any error message here */
|
||||
const u8 *aBlob, /* Pointer to in-memory file image */
|
||||
int nBlob, /* Size of aBlob[] in bytes */
|
||||
i64 nBlob, /* Size of aBlob[] in bytes */
|
||||
FILE *pFile, /* If aBlob==0, read from this file */
|
||||
i64 iOff, /* Offset of CDS record */
|
||||
ZipfileEntry **ppEntry /* OUT: Pointer to new object */
|
||||
@@ -868,14 +875,14 @@ static int zipfileGetEntry(
|
||||
memset(pNew, 0, sizeof(ZipfileEntry));
|
||||
rc = zipfileReadCDS(aRead, &pNew->cds);
|
||||
if( rc!=SQLITE_OK ){
|
||||
*pzErr = sqlite3_mprintf("failed to read CDS at offset %lld", iOff);
|
||||
zipfileTableErr(pTab, "failed to read CDS at offset %lld", iOff);
|
||||
}else if( aBlob==0 ){
|
||||
rc = zipfileReadData(
|
||||
pFile, aRead, nExtra+nFile, iOff+ZIPFILE_CDS_FIXED_SZ, pzErr
|
||||
);
|
||||
}else{
|
||||
aRead = (u8*)&aBlob[iOff + ZIPFILE_CDS_FIXED_SZ];
|
||||
if( (iOff + ZIPFILE_LFH_FIXED_SZ + nFile + nExtra)>nBlob ){
|
||||
if( (iOff + ZIPFILE_CDS_FIXED_SZ + nFile + nExtra)>nBlob ){
|
||||
rc = zipfileCorrupt(pzErr);
|
||||
}
|
||||
}
|
||||
@@ -900,7 +907,7 @@ static int zipfileGetEntry(
|
||||
rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr);
|
||||
}else{
|
||||
aRead = (u8*)&aBlob[pNew->cds.iOffset];
|
||||
if( (pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ)>(unsigned)nBlob ){
|
||||
if( ((i64)pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ)>nBlob ){
|
||||
rc = zipfileCorrupt(pzErr);
|
||||
}
|
||||
}
|
||||
@@ -908,7 +915,7 @@ static int zipfileGetEntry(
|
||||
memset(&lfh, 0, sizeof(lfh));
|
||||
if( rc==SQLITE_OK ) rc = zipfileReadLFH(aRead, &lfh);
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ;
|
||||
pNew->iDataOff = (i64)pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ;
|
||||
pNew->iDataOff += lfh.nFile + lfh.nExtra;
|
||||
if( aBlob && pNew->cds.szCompressed ){
|
||||
if( pNew->iDataOff + pNew->cds.szCompressed > nBlob ){
|
||||
@@ -919,7 +926,7 @@ static int zipfileGetEntry(
|
||||
}
|
||||
}
|
||||
}else{
|
||||
*pzErr = sqlite3_mprintf("failed to read LFH at offset %d",
|
||||
zipfileTableErr(pTab, "failed to read LFH at offset %d",
|
||||
(int)pNew->cds.iOffset
|
||||
);
|
||||
}
|
||||
@@ -943,7 +950,7 @@ static int zipfileNext(sqlite3_vtab_cursor *cur){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
if( pCsr->pFile ){
|
||||
i64 iEof = pCsr->eocd.iOffset + pCsr->eocd.nSize;
|
||||
i64 iEof = (i64)pCsr->eocd.iOffset + (i64)pCsr->eocd.nSize;
|
||||
zipfileEntryFree(pCsr->pCurrent);
|
||||
pCsr->pCurrent = 0;
|
||||
if( pCsr->iNextOff>=iEof ){
|
||||
@@ -1181,12 +1188,12 @@ static int zipfileEof(sqlite3_vtab_cursor *cur){
|
||||
static int zipfileReadEOCD(
|
||||
ZipfileTab *pTab, /* Return errors here */
|
||||
const u8 *aBlob, /* Pointer to in-memory file image */
|
||||
int nBlob, /* Size of aBlob[] in bytes */
|
||||
i64 nBlob, /* Size of aBlob[] in bytes */
|
||||
FILE *pFile, /* Read from this file if aBlob==0 */
|
||||
ZipfileEOCD *pEOCD /* Object to populate */
|
||||
){
|
||||
u8 *aRead = pTab->aBuffer; /* Temporary buffer */
|
||||
int nRead; /* Bytes to read from file */
|
||||
i64 nRead; /* Bytes to read from file */
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
memset(pEOCD, 0, sizeof(ZipfileEOCD));
|
||||
@@ -1207,7 +1214,7 @@ static int zipfileReadEOCD(
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
int i;
|
||||
i64 i;
|
||||
|
||||
/* Scan backwards looking for the signature bytes */
|
||||
for(i=nRead-20; i>=0; i--){
|
||||
@@ -1218,9 +1225,7 @@ static int zipfileReadEOCD(
|
||||
}
|
||||
}
|
||||
if( i<0 ){
|
||||
pTab->base.zErrMsg = sqlite3_mprintf(
|
||||
"cannot find end of central directory record"
|
||||
);
|
||||
zipfileTableErr(pTab, "cannot find end of central directory record");
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
@@ -1265,7 +1270,7 @@ static void zipfileAddEntry(
|
||||
}
|
||||
}
|
||||
|
||||
static int zipfileLoadDirectory(ZipfileTab *pTab, const u8 *aBlob, int nBlob){
|
||||
static int zipfileLoadDirectory(ZipfileTab *pTab, const u8 *aBlob, i64 nBlob){
|
||||
ZipfileEOCD eocd;
|
||||
int rc;
|
||||
int i;
|
||||
@@ -1313,7 +1318,7 @@ static int zipfileFilter(
|
||||
}else if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){
|
||||
static const u8 aEmptyBlob = 0;
|
||||
const u8 *aBlob = (const u8*)sqlite3_value_blob(argv[0]);
|
||||
int nBlob = sqlite3_value_bytes(argv[0]);
|
||||
i64 nBlob = sqlite3_value_bytes(argv[0]);
|
||||
assert( pTab->pFirstEntry==0 );
|
||||
if( aBlob==0 ){
|
||||
aBlob = &aEmptyBlob;
|
||||
@@ -1511,7 +1516,7 @@ static int zipfileBegin(sqlite3_vtab *pVtab){
|
||||
|
||||
assert( pTab->pWriteFd==0 );
|
||||
if( pTab->zFile==0 || pTab->zFile[0]==0 ){
|
||||
pTab->base.zErrMsg = sqlite3_mprintf("zipfile: missing filename");
|
||||
zipfileTableErr(pTab, "zipfile: missing filename");
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
@@ -1521,9 +1526,9 @@ static int zipfileBegin(sqlite3_vtab *pVtab){
|
||||
** in main-memory until the transaction is committed. */
|
||||
pTab->pWriteFd = sqlite3_fopen(pTab->zFile, "ab+");
|
||||
if( pTab->pWriteFd==0 ){
|
||||
pTab->base.zErrMsg = sqlite3_mprintf(
|
||||
"zipfile: failed to open file %s for writing", pTab->zFile
|
||||
);
|
||||
zipfileTableErr(pTab,
|
||||
"zipfile: failed to open file %s for writing", pTab->zFile
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
fseek(pTab->pWriteFd, 0, SEEK_END);
|
||||
@@ -1988,7 +1993,7 @@ struct ZipfileCtx {
|
||||
ZipfileBuffer cds;
|
||||
};
|
||||
|
||||
static int zipfileBufferGrow(ZipfileBuffer *pBuf, int nByte){
|
||||
static int zipfileBufferGrow(ZipfileBuffer *pBuf, i64 nByte){
|
||||
if( pBuf->n+nByte>pBuf->nAlloc ){
|
||||
u8 *aNew;
|
||||
sqlite3_int64 nNew = pBuf->n ? pBuf->n*2 : 512;
|
||||
@@ -2037,7 +2042,7 @@ static void zipfileStep(sqlite3_context *pCtx, int nVal, sqlite3_value **apVal){
|
||||
char *zName = 0; /* Path (name) of new entry */
|
||||
int nName = 0; /* Size of zName in bytes */
|
||||
char *zFree = 0; /* Free this before returning */
|
||||
int nByte;
|
||||
i64 nByte;
|
||||
|
||||
memset(&e, 0, sizeof(e));
|
||||
p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx));
|
||||
|
||||
+56
-24
@@ -90,16 +90,10 @@ while( zSql && zSql[0] ){
|
||||
}
|
||||
~~~
|
||||
|
||||
<a id="spec"></a>
|
||||
## 2.0 The `sqlite3_qrf_spec` object
|
||||
|
||||
The most recent definition of `sqlite3_qrf_spec` is shown below.
|
||||
|
||||
Do not be alarmed by the complexity of this structure. You only need
|
||||
to understand the properties you want to modify. Zero is always a good
|
||||
default for all of the attributes (except iVersion and pzOutput/xWrite)
|
||||
and so simply zeroing out the bulk of this structure is a good start.
|
||||
You can then slowly make adjustments to individual fields to get the
|
||||
results you desire.
|
||||
The `sqlite3_qrf_spec` looks like this:
|
||||
|
||||
> ~~~
|
||||
typedef struct sqlite3_qrf_spec sqlite3_qrf_spec;
|
||||
@@ -120,6 +114,8 @@ struct sqlite3_qrf_spec {
|
||||
short int nWrap; /* Wrap columns wider than this */
|
||||
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[] */
|
||||
@@ -138,16 +134,19 @@ struct sqlite3_qrf_spec {
|
||||
};
|
||||
~~~
|
||||
|
||||
Again, the only fields that must initialized are:
|
||||
Do not be alarmed by the complexity of this structure. Everything can
|
||||
be zeroed except for:
|
||||
|
||||
* `.iVersion`
|
||||
* One of `.pzOutput` or `.xWrite`.
|
||||
|
||||
All other fields can be zeroed. Or they can contain other values to
|
||||
alter the formatting of the query results.
|
||||
You do not need to understand and configure every field of this object
|
||||
in order to use QRF effectively. Start by zeroing out the whole structure,
|
||||
then initializing iVersion and one of pzOutput or xWrite. Then maybe
|
||||
tweak one or two other settings to get the output you want.
|
||||
|
||||
Further detail on the meanings of each of the fields in the
|
||||
`sqlite3_qrf_spec` object are described below.
|
||||
`sqlite3_qrf_spec` object is in the subsequent sections.
|
||||
|
||||
### 2.1 Structure Version Number
|
||||
|
||||
@@ -346,7 +345,7 @@ A value of QRF_BLOB_Size does not show any BLOB content at all.
|
||||
Instead, it substitutes a text string that says how many bytes
|
||||
the BLOB contains.
|
||||
|
||||
### 2.8 Maximum size of displayed content (nLineLimit, nCharLimit)
|
||||
### 2.8 Maximum size of displayed content (nLineLimit, nCharLimit, nTitleLimit)
|
||||
|
||||
If the sqlite3_qrf_spec.nCharLimit setting is non-zero, then the formatter
|
||||
will display only the first nCharLimit characters of each value.
|
||||
@@ -362,14 +361,37 @@ If the sqlite3_qrf_spec.nLineLimit setting is non-zero, then the
|
||||
formatter will only display the first nLineLimit lines of each value.
|
||||
It does not matter if the value is split because it contains a newline
|
||||
character, or if it split by wrapping. This setting merely limits
|
||||
the number of displayed lines. This setting only works for
|
||||
**Box**, **Column**, **Line**, **Markdown**, and **Table** styles.
|
||||
the number of displayed lines. The nLineLimit setting currently only
|
||||
works for **Box**, **Column**, **Line**, **Markdown**, and **Table**
|
||||
styles, though that limitation might change in future releases.
|
||||
|
||||
The idea behind both of these settings is to prevent large renderings
|
||||
when doing a query that (unexpectedly) contains very large text or
|
||||
blob values: perhaps megabyes of text.
|
||||
|
||||
### 2.9 Word Wrapping In Columnar Styles (nWrap, bWordWrap)
|
||||
If the sqlite3_qrf_spec.nTitleLimit is non-zero, then the formatter
|
||||
attempts to limits the size of column titles to at most nTitleLimit
|
||||
display characters in width and a single line of text. The nTitleLimit
|
||||
is useful for queries that have result columns that are scalar
|
||||
subqueries or complex expressions. If those columns lack an AS
|
||||
clause, then the name of the column will be a copy of the expression
|
||||
that defines the column, which in some queries can be hundreds of
|
||||
characters and multiple lines in length, which can reduce the readability
|
||||
of tabular displays. An nTitleLimit somewhere in the range of 10 to 20.
|
||||
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 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
|
||||
@@ -388,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.
|
||||
@@ -408,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
|
||||
@@ -446,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
|
||||
@@ -525,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
|
||||
@@ -533,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
|
||||
@@ -565,6 +587,13 @@ The eText, eBlob, and eEsc settings above become no-ops if the xRender
|
||||
routine returns non-NULL. In other words, the application-supplied
|
||||
xRender routine is expected to do all of its own quoting and formatting.
|
||||
|
||||
The xRender routine is expected to do character length limiting itself.
|
||||
So the nCharLimit setting becomes a no-op if xRender is used. However
|
||||
the nLineLimit setting is still applied. The nTitleLimit setting is
|
||||
not applicable to xRender because title values come from the
|
||||
sqlite3_column_name() interface not from sqlite3_column_value(),
|
||||
and so that names of columns are never processed by xRender.
|
||||
|
||||
## 3.0 The `sqlite3_format_query_result()` Interface
|
||||
|
||||
Invoke the `sqlite3_format_query_result(P,S,E)` interface to run
|
||||
@@ -690,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
|
||||
|
||||
+248
-59
@@ -17,6 +17,7 @@
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef sqlite3_int64 i64;
|
||||
|
||||
@@ -34,7 +35,8 @@ typedef struct qrfEQPGraph qrfEQPGraph;
|
||||
struct qrfEQPGraph {
|
||||
qrfEQPGraphRow *pRow; /* Linked list of all rows of the EQP output */
|
||||
qrfEQPGraphRow *pLast; /* Last element of the pRow list */
|
||||
char zPrefix[100]; /* Graph prefix */
|
||||
int nWidth; /* Width of the graph */
|
||||
char zPrefix[400]; /* Graph prefix */
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -56,7 +58,7 @@ struct Qrf {
|
||||
union {
|
||||
struct { /* Content for QRF_STYLE_Line */
|
||||
int mxColWth; /* Maximum display width of any column */
|
||||
const char **azCol; /* Names of output columns (MODE_Line) */
|
||||
char **azCol; /* Names of output columns (MODE_Line) */
|
||||
} sLine;
|
||||
qrfEQPGraph *pGraph; /* EQP graph (Eqp, Stats, and StatsEst) */
|
||||
struct { /* Content for QRF_STYLE_Explain */
|
||||
@@ -64,6 +66,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 */
|
||||
@@ -140,6 +143,15 @@ static void qrfOom(Qrf *p){
|
||||
qrfError(p, SQLITE_NOMEM, "out of memory");
|
||||
}
|
||||
|
||||
/*
|
||||
** Transfer any error in pStr over into p.
|
||||
*/
|
||||
static void qrfStrErr(Qrf *p, sqlite3_str *pStr){
|
||||
int rc = pStr ? sqlite3_str_errcode(pStr) : 0;
|
||||
if( rc ){
|
||||
qrfError(p, rc, sqlite3_errstr(rc));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@@ -220,6 +232,45 @@ static void qrfEqpRenderLevel(Qrf *p, int iEqpId){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Render the 64-bit value N in a more human-readable format into
|
||||
** pOut.
|
||||
**
|
||||
** + Only show the first three significant digits.
|
||||
** + Append suffixes K, M, G, T, P, and E for 1e3, 1e6, ... 1e18
|
||||
*/
|
||||
static void qrfApproxInt64(sqlite3_str *pOut, i64 N){
|
||||
static const char aSuffix[] = { 'K', 'M', 'G', 'T', 'P', 'E' };
|
||||
int i;
|
||||
if( N<0 ){
|
||||
N = N==INT64_MIN ? INT64_MAX : -N;
|
||||
sqlite3_str_append(pOut, "-", 1);
|
||||
}
|
||||
if( N<10000 ){
|
||||
sqlite3_str_appendf(pOut, "%4lld ", N);
|
||||
return;
|
||||
}
|
||||
for(i=1; i<=18; i++){
|
||||
N = (N+5)/10;
|
||||
if( N<10000 ){
|
||||
int n = (int)N;
|
||||
switch( i%3 ){
|
||||
case 0:
|
||||
sqlite3_str_appendf(pOut, "%d.%02d", n/1000, (n%1000)/10);
|
||||
break;
|
||||
case 1:
|
||||
sqlite3_str_appendf(pOut, "%2d.%d", n/100, (n%100)/10);
|
||||
break;
|
||||
case 2:
|
||||
sqlite3_str_appendf(pOut, "%4d", n/10);
|
||||
break;
|
||||
}
|
||||
sqlite3_str_append(pOut, &aSuffix[i/3], 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Display and reset the EXPLAIN QUERY PLAN data
|
||||
*/
|
||||
@@ -235,7 +286,26 @@ static void qrfEqpRender(Qrf *p, i64 nCycle){
|
||||
p->u.pGraph->pRow = pRow->pNext;
|
||||
sqlite3_free(pRow);
|
||||
}else if( nCycle>0 ){
|
||||
sqlite3_str_appendf(p->pOut, "QUERY PLAN (cycles=%lld [100%%])\n",nCycle);
|
||||
int nSp = p->u.pGraph->nWidth - 2;
|
||||
if( p->spec.eStyle==QRF_STYLE_StatsEst ){
|
||||
sqlite3_str_appendchar(p->pOut, nSp, ' ');
|
||||
sqlite3_str_appendall(p->pOut,
|
||||
"Cycles Loops (est) Rows (est)\n");
|
||||
sqlite3_str_appendchar(p->pOut, nSp, ' ');
|
||||
sqlite3_str_appendall(p->pOut,
|
||||
"---------- ------------ ------------\n");
|
||||
}else{
|
||||
sqlite3_str_appendchar(p->pOut, nSp, ' ');
|
||||
sqlite3_str_appendall(p->pOut,
|
||||
"Cycles Loops Rows \n");
|
||||
sqlite3_str_appendchar(p->pOut, nSp, ' ');
|
||||
sqlite3_str_appendall(p->pOut,
|
||||
"---------- ----- -----\n");
|
||||
}
|
||||
sqlite3_str_appendall(p->pOut, "QUERY PLAN");
|
||||
sqlite3_str_appendchar(p->pOut, nSp - 10, ' ');
|
||||
qrfApproxInt64(p->pOut, nCycle);
|
||||
sqlite3_str_appendall(p->pOut, " 100%\n");
|
||||
}else{
|
||||
sqlite3_str_appendall(p->pOut, "QUERY PLAN\n");
|
||||
}
|
||||
@@ -290,6 +360,8 @@ static void qrfEqpStats(Qrf *p){
|
||||
int i = 0;
|
||||
i64 nTotal = 0;
|
||||
int nWidth = 0;
|
||||
int prevPid = -1; /* Previous iPid */
|
||||
double rEstCum = 1.0; /* Cumulative row estimate */
|
||||
sqlite3_str *pLine = sqlite3_str_new(p->db);
|
||||
sqlite3_str *pStats = sqlite3_str_new(p->db);
|
||||
qrfEqpReset(p);
|
||||
@@ -303,7 +375,7 @@ static void qrfEqpStats(Qrf *p){
|
||||
n = (int)strlen(z) + qrfStatsHeight(pS,i)*3;
|
||||
if( n>nWidth ) nWidth = n;
|
||||
}
|
||||
nWidth += 4;
|
||||
nWidth += 2;
|
||||
|
||||
sqlite3_stmt_scanstatus_v2(pS,-1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal);
|
||||
for(i=0; 1; i++){
|
||||
@@ -319,40 +391,49 @@ static void qrfEqpStats(Qrf *p){
|
||||
if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&zo) ){
|
||||
break;
|
||||
}
|
||||
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid);
|
||||
if( iPid!=prevPid ){
|
||||
prevPid = iPid;
|
||||
rEstCum = 1.0;
|
||||
}
|
||||
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_EST,f,(void*)&rEst);
|
||||
rEstCum *= rEst;
|
||||
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop);
|
||||
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow);
|
||||
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle);
|
||||
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId);
|
||||
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid);
|
||||
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NAME,f,(void*)&zName);
|
||||
|
||||
if( nCycle>=0 || nLoop>=0 || nRow>=0 ){
|
||||
const char *zSp = "";
|
||||
double rpl;
|
||||
int nSp = 0;
|
||||
sqlite3_str_reset(pStats);
|
||||
if( nCycle>=0 && nTotal>0 ){
|
||||
sqlite3_str_appendf(pStats, "cycles=%lld [%d%%]",
|
||||
nCycle, ((nCycle*100)+nTotal/2) / nTotal
|
||||
qrfApproxInt64(pStats, nCycle);
|
||||
sqlite3_str_appendf(pStats, " %3d%%",
|
||||
((nCycle*100)+nTotal/2) / nTotal
|
||||
);
|
||||
zSp = " ";
|
||||
nSp = 2;
|
||||
}
|
||||
if( nLoop>=0 ){
|
||||
sqlite3_str_appendf(pStats, "%sloops=%lld", zSp, nLoop);
|
||||
zSp = " ";
|
||||
if( nSp ) sqlite3_str_appendchar(pStats, nSp, ' ');
|
||||
qrfApproxInt64(pStats, nLoop);
|
||||
nSp = 2;
|
||||
if( p->spec.eStyle==QRF_STYLE_StatsEst ){
|
||||
sqlite3_str_appendf(pStats, " ");
|
||||
qrfApproxInt64(pStats, (i64)(rEstCum/rEst));
|
||||
}
|
||||
}
|
||||
if( nRow>=0 ){
|
||||
sqlite3_str_appendf(pStats, "%srows=%lld", zSp, nRow);
|
||||
zSp = " ";
|
||||
if( nSp ) sqlite3_str_appendchar(pStats, nSp, ' ');
|
||||
qrfApproxInt64(pStats, nRow);
|
||||
nSp = 2;
|
||||
if( p->spec.eStyle==QRF_STYLE_StatsEst ){
|
||||
sqlite3_str_appendf(pStats, " ");
|
||||
qrfApproxInt64(pStats, (i64)rEstCum);
|
||||
}
|
||||
}
|
||||
|
||||
if( p->spec.eStyle==QRF_STYLE_StatsEst ){
|
||||
rpl = (double)nRow / (double)nLoop;
|
||||
sqlite3_str_appendf(pStats, "%srpl=%.1f est=%.1f", zSp, rpl, rEst);
|
||||
}
|
||||
|
||||
sqlite3_str_appendf(pLine,
|
||||
"% *s (%s)", -1*(nWidth-qrfStatsHeight(pS,i)*3), zo,
|
||||
"% *s %s", -1*(nWidth-qrfStatsHeight(pS,i)*3), zo,
|
||||
sqlite3_str_value(pStats)
|
||||
);
|
||||
sqlite3_str_reset(pStats);
|
||||
@@ -362,7 +443,10 @@ static void qrfEqpStats(Qrf *p){
|
||||
qrfEqpAppend(p, iId, iPid, zo);
|
||||
}
|
||||
}
|
||||
if( p->u.pGraph ) p->u.pGraph->nWidth = nWidth;
|
||||
qrfStrErr(p, pLine);
|
||||
sqlite3_free(sqlite3_str_finish(pLine));
|
||||
qrfStrErr(p, pStats);
|
||||
sqlite3_free(sqlite3_str_finish(pStats));
|
||||
#endif
|
||||
}
|
||||
@@ -726,9 +810,11 @@ static void qrfEscape(
|
||||
static int qrfRelaxable(Qrf *p, const char *z){
|
||||
size_t i, n;
|
||||
if( z[0]=='\'' || qrfSpace(z[0]) ) return 0;
|
||||
if( z[0]==0 && (p->spec.zNull==0 || p->spec.zNull[0]==0) ) return 0;
|
||||
if( z[0]==0 ){
|
||||
return (p->spec.zNull!=0 && p->spec.zNull[0]!=0);
|
||||
}
|
||||
n = strlen(z);
|
||||
if( z[n-1]=='\'' || qrfSpace(z[n-1]) ) return 0;
|
||||
if( n==0 || z[n-1]=='\'' || qrfSpace(z[n-1]) ) return 0;
|
||||
if( p->spec.zNull && strcmp(p->spec.zNull,z)==0 ) return 0;
|
||||
i = (z[0]=='-' || z[0]=='+');
|
||||
if( strcmp(z+i,"Inf")==0 ) return 0;
|
||||
@@ -945,6 +1031,53 @@ static const char *qrfJsonbToJson(Qrf *p, int iCol){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Adjust the input string zIn[] such that it is no more than N display
|
||||
** characters wide. If it is wider than that, then truncate and add
|
||||
** ellipsis. Or if zIn[] contains a \r or \n, truncate at that point,
|
||||
** adding ellipsis. Embedded tabs in zIn[] are converted into ordinary
|
||||
** spaces.
|
||||
**
|
||||
** Return this display width of the modified title string.
|
||||
*/
|
||||
static int qrfTitleLimit(char *zIn, int N){
|
||||
unsigned char *z = (unsigned char*)zIn;
|
||||
int n = 0;
|
||||
unsigned char *zEllipsis = 0;
|
||||
while( 1 /*exit-by-break*/ ){
|
||||
if( z[0]<' ' ){
|
||||
int k;
|
||||
if( z[0]==0 ){
|
||||
zEllipsis = 0;
|
||||
break;
|
||||
}else if( z[0]=='\033' && (k = qrfIsVt100(z))>0 ){
|
||||
z += k;
|
||||
}else if( z[0]=='\t' ){
|
||||
z[0] = ' ';
|
||||
}else if( z[0]=='\n' || z[0]=='\r' ){
|
||||
z[0] = ' ';
|
||||
}else{
|
||||
z++;
|
||||
}
|
||||
}else if( (0x80&z[0])==0 ){
|
||||
if( n>=(N-3) && zEllipsis==0 ) zEllipsis = z;
|
||||
if( n==N ){ z[0] = 0; break; }
|
||||
n++;
|
||||
z++;
|
||||
}else{
|
||||
int u = 0;
|
||||
int len = sqlite3_qrf_decode_utf8(z, &u);
|
||||
if( n+len>(N-3) && zEllipsis==0 ) zEllipsis = z;
|
||||
if( n+len>N ){ z[0] = 0; break; }
|
||||
z += len;
|
||||
n += sqlite3_qrf_wcwidth(u);
|
||||
}
|
||||
}
|
||||
if( zEllipsis && N>=3 ) memcpy(zEllipsis,"...",4);
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Render value pVal into pOut
|
||||
*/
|
||||
@@ -1827,8 +1960,15 @@ static void qrfColumnar(Qrf *p){
|
||||
pStr = sqlite3_str_new(p->db);
|
||||
qrfEncodeText(p, pStr, z ? z : "");
|
||||
n = sqlite3_str_length(pStr);
|
||||
qrfStrErr(p, pStr);
|
||||
z = data.az[data.n] = sqlite3_str_finish(pStr);
|
||||
data.aiWth[data.n] = w = qrfDisplayWidth(z, n, &nNL);
|
||||
if( p->spec.nTitleLimit ){
|
||||
nNL = 0;
|
||||
data.aiWth[data.n] = w = qrfTitleLimit(data.az[data.n],
|
||||
p->spec.nTitleLimit );
|
||||
}else{
|
||||
data.aiWth[data.n] = w = qrfDisplayWidth(z, n, &nNL);
|
||||
}
|
||||
data.n++;
|
||||
if( w>data.a[i].mxW ) data.a[i].mxW = w;
|
||||
if( nNL ) data.bMultiRow = 1;
|
||||
@@ -1848,6 +1988,7 @@ static void qrfColumnar(Qrf *p){
|
||||
pStr = sqlite3_str_new(p->db);
|
||||
qrfRenderValue(p, pStr, i);
|
||||
n = sqlite3_str_length(pStr);
|
||||
qrfStrErr(p, pStr);
|
||||
z = data.az[data.n] = sqlite3_str_finish(pStr);
|
||||
data.abNum[data.n] = eType==SQLITE_INTEGER || eType==SQLITE_FLOAT;
|
||||
data.aiWth[data.n] = w = qrfDisplayWidth(z, n, &nNL);
|
||||
@@ -2002,7 +2143,7 @@ static void qrfColumnar(Qrf *p){
|
||||
}else{
|
||||
bRTrim = 0;
|
||||
}
|
||||
for(i=0; i<data.n; i+=nColumn){
|
||||
for(i=0; i<data.n && sqlite3_str_errcode(p->pOut)==SQLITE_OK; i+=nColumn){
|
||||
int bMore;
|
||||
int nRow = 0;
|
||||
|
||||
@@ -2189,7 +2330,7 @@ static void qrfExplain(Qrf *p){
|
||||
assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 2), "p1" ) );
|
||||
assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 3), "p2" ) );
|
||||
|
||||
for(iOp=0; SQLITE_ROW==sqlite3_step(p->pStmt); iOp++){
|
||||
for(iOp=0; SQLITE_ROW==sqlite3_step(p->pStmt) && !p->iErr; iOp++){
|
||||
int iAddr = sqlite3_column_int(p->pStmt, 0);
|
||||
const char *zOp = (const char*)sqlite3_column_text(p->pStmt, 1);
|
||||
int p1 = sqlite3_column_int(p->pStmt, 2);
|
||||
@@ -2246,7 +2387,7 @@ static void qrfExplain(Qrf *p){
|
||||
}
|
||||
if( nArg>nWidth ) nArg = nWidth;
|
||||
|
||||
for(iOp=0; sqlite3_step(p->pStmt)==SQLITE_ROW; iOp++){
|
||||
for(iOp=0; sqlite3_step(p->pStmt)==SQLITE_ROW && !p->iErr; iOp++){
|
||||
/* If this is the first row seen, print out the headers */
|
||||
if( iOp==0 ){
|
||||
for(i=0; i<nArg; i++){
|
||||
@@ -2423,30 +2564,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;
|
||||
}
|
||||
@@ -2454,6 +2611,7 @@ static void qrfOneSimpleRow(Qrf *p){
|
||||
sqlite3_str *pVal;
|
||||
int mxW;
|
||||
int bWW;
|
||||
int nSep;
|
||||
if( p->u.sLine.azCol==0 ){
|
||||
p->u.sLine.azCol = sqlite3_malloc64( p->nCol*sizeof(char*) );
|
||||
if( p->u.sLine.azCol==0 ){
|
||||
@@ -2463,28 +2621,35 @@ static void qrfOneSimpleRow(Qrf *p){
|
||||
p->u.sLine.mxColWth = 0;
|
||||
for(i=0; i<p->nCol; i++){
|
||||
int sz;
|
||||
p->u.sLine.azCol[i] = sqlite3_column_name(p->pStmt, i);
|
||||
if( p->u.sLine.azCol[i]==0 ) p->u.sLine.azCol[i] = "unknown";
|
||||
const char *zCName = sqlite3_column_name(p->pStmt, i);
|
||||
if( zCName==0 ) zCName = "unknown";
|
||||
p->u.sLine.azCol[i] = sqlite3_mprintf("%s", zCName);
|
||||
if( p->spec.nTitleLimit>0 ){
|
||||
(void)qrfTitleLimit(p->u.sLine.azCol[i], p->spec.nTitleLimit);
|
||||
}
|
||||
sz = (int)sqlite3_qrf_wcswidth(p->u.sLine.azCol[i]);
|
||||
if( sz > p->u.sLine.mxColWth ) p->u.sLine.mxColWth = sz;
|
||||
}
|
||||
}
|
||||
if( p->nRow ) sqlite3_str_append(p->pOut, "\n", 1);
|
||||
pVal = sqlite3_str_new(p->db);
|
||||
mxW = p->mxWidth - (3 + p->u.sLine.mxColWth);
|
||||
nSep = (int)strlen(p->spec.zColumnSep);
|
||||
mxW = p->mxWidth - (nSep + p->u.sLine.mxColWth);
|
||||
bWW = p->spec.bWordWrap==QRF_Yes;
|
||||
for(i=0; i<p->nCol; i++){
|
||||
const char *zVal;
|
||||
int cnt = 0;
|
||||
qrfWidthPrint(p, p->pOut, -p->u.sLine.mxColWth, p->u.sLine.azCol[i]);
|
||||
sqlite3_str_append(p->pOut, " = ", 3);
|
||||
sqlite3_str_append(p->pOut, p->spec.zColumnSep, nSep);
|
||||
qrfRenderValue(p, pVal, i);
|
||||
zVal = sqlite3_str_value(pVal);
|
||||
if( zVal==0 ) zVal = "";
|
||||
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 = "...";
|
||||
@@ -2496,6 +2661,7 @@ static void qrfOneSimpleRow(Qrf *p){
|
||||
}while( zVal[0] );
|
||||
sqlite3_str_reset(pVal);
|
||||
}
|
||||
qrfStrErr(p, pVal);
|
||||
sqlite3_free(sqlite3_str_finish(pVal));
|
||||
qrfWrite(p);
|
||||
break;
|
||||
@@ -2546,7 +2712,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);
|
||||
@@ -2559,7 +2725,7 @@ static void qrfInitialize(
|
||||
qrfOom(p);
|
||||
return;
|
||||
}
|
||||
p->iErr = 0;
|
||||
p->iErr = SQLITE_OK;
|
||||
p->nCol = sqlite3_column_count(p->pStmt);
|
||||
p->nRow = 0;
|
||||
sz = sizeof(sqlite3_qrf_spec);
|
||||
@@ -2606,6 +2772,13 @@ 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: {
|
||||
if( p->spec.zColumnSep==0 ){
|
||||
p->spec.zColumnSep = ": ";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QRF_STYLE_Csv: {
|
||||
@@ -2698,42 +2871,58 @@ 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;
|
||||
}
|
||||
case QRF_STYLE_Line: {
|
||||
if( p->u.sLine.azCol ) sqlite3_free(p->u.sLine.azCol);
|
||||
if( p->u.sLine.azCol ){
|
||||
int i;
|
||||
for(i=0; i<p->nCol; i++) sqlite3_free(p->u.sLine.azCol[i]);
|
||||
sqlite3_free(p->u.sLine.azCol);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QRF_STYLE_Stats:
|
||||
case QRF_STYLE_StatsEst:
|
||||
case QRF_STYLE_StatsEst: {
|
||||
i64 nCycle = 0;
|
||||
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
|
||||
sqlite3_stmt_scanstatus_v2(p->pStmt, -1, SQLITE_SCANSTAT_NCYCLE,
|
||||
SQLITE_SCANSTAT_COMPLEX, (void*)&nCycle);
|
||||
#endif
|
||||
qrfEqpRender(p, nCycle);
|
||||
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] ){
|
||||
sqlite3_int64 n, sz;
|
||||
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;
|
||||
|
||||
@@ -41,6 +41,8 @@ struct sqlite3_qrf_spec {
|
||||
short int nWrap; /* Wrap columns wider than this */
|
||||
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[] */
|
||||
|
||||
@@ -192,4 +192,32 @@ do_test 4.7.2 {
|
||||
list [catch {rbu close} msg] $msg
|
||||
} {1 {SQLITE_ERROR - rbu_state mismatch error}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# https://sqlite.org/forum/info/6d0a31e22a435877
|
||||
#
|
||||
reset_db
|
||||
forcedelete rbu.db
|
||||
|
||||
do_execsql_test 5.0 {
|
||||
CREATE TABLE t1(a BLOB);
|
||||
INSERT INTO t1 VALUES(x'41');
|
||||
}
|
||||
|
||||
sqlite3 dbRbu rbu.db
|
||||
dbRbu eval {
|
||||
CREATE TABLE data_t1(a, rbu_control, rbu_rowid);
|
||||
INSERT INTO data_t1 VALUES(X'310a313a5a337e7e7e7e7e40302c303b','f',1);
|
||||
}
|
||||
dbRbu close
|
||||
|
||||
do_test 5.1 {
|
||||
sqlite3rbu rbu test.db rbu.db
|
||||
rbu step
|
||||
} {SQLITE_ERROR}
|
||||
|
||||
do_test 5.2 {
|
||||
list [catch {rbu close} msg] $msg
|
||||
} {1 {SQLITE_ERROR - corrupt fossil delta}}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -623,7 +623,7 @@ static int rbuDeltaApply(
|
||||
/* ERROR: copy exceeds output file size */
|
||||
return -1;
|
||||
}
|
||||
if( (int)(ofst+cnt) > lenSrc ){
|
||||
if( (u64)ofst+(u64)cnt > (u64)lenSrc ){
|
||||
/* ERROR: copy extends past end of input */
|
||||
return -1;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+14
-13
@@ -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{
|
||||
@@ -3775,7 +3776,7 @@ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){
|
||||
if( node.zData==0 ) return;
|
||||
nData = sqlite3_value_bytes(apArg[1]);
|
||||
if( nData<4 ) return;
|
||||
if( nData<NCELL(&node)*tree.nBytesPerCell ) return;
|
||||
if( nData<4+NCELL(&node)*tree.nBytesPerCell ) return;
|
||||
|
||||
pOut = sqlite3_str_new(0);
|
||||
for(ii=0; ii<NCELL(&node); ii++){
|
||||
|
||||
@@ -47,4 +47,14 @@ ifcapable rtree_int_only {
|
||||
|
||||
do_rtree_integrity_test rtreeB-1.2 t1
|
||||
|
||||
# https://sqlite.org/forum/forumpost/2026-01-08T23:32:19Z
|
||||
#
|
||||
db null NULL
|
||||
do_execsql_test rtreeB-2.1 {
|
||||
SELECT rtreenode(1,x'00000001'||randomblob(15)) IS NULL;
|
||||
} {1}
|
||||
do_execsql_test rtreeB-2.2 {
|
||||
SELECT rtreenode(1,x'00000001'||randomblob(16)) IS NOT NULL;
|
||||
} {1}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -135,6 +135,7 @@ foreach {tn blob} {
|
||||
54 540101743400120003001200010000000000000002120002400C000000000002120002400C00000000000050040100000074310017FF0050040100000074310017FF7F00000000000000050100000000000000030100000003001700010000666F7572
|
||||
55 540101743400120003001200010000000000000002120002400C00000000000050040100000074310017000100010080000001000000020003010100000300170100000003001700010000666F7572
|
||||
56 5487ffffff7f
|
||||
57 54015052494e5446110017120004
|
||||
} {
|
||||
do_test 2.$tn {
|
||||
set changeset [binary decode hex $blob]
|
||||
|
||||
@@ -192,6 +192,16 @@ do_test 3.3 {
|
||||
}
|
||||
} {1 1 3 3}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
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}
|
||||
grp delete
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -349,6 +349,20 @@ static int sessionVarintGet(const u8 *aBuf, int *piVal){
|
||||
return getVarint32(aBuf, *piVal);
|
||||
}
|
||||
|
||||
/*
|
||||
** Read a varint value from buffer aBuf[], size nBuf bytes, into *piVal.
|
||||
** Return the number of bytes read.
|
||||
*/
|
||||
static int sessionVarintGetSafe(const u8 *aBuf, int nBuf, int *piVal){
|
||||
u8 aCopy[5];
|
||||
const u8 *aRead = aBuf;
|
||||
if( nBuf<5 ){
|
||||
memcpy(aCopy, aBuf, nBuf);
|
||||
aRead = aCopy;
|
||||
}
|
||||
return getVarint32(aRead, *piVal);
|
||||
}
|
||||
|
||||
/* Load an unaligned and unsigned 32-bit integer */
|
||||
#define SESSION_UINT32(x) (((u32)(x)[0]<<24)|((x)[1]<<16)|((x)[2]<<8)|(x)[3])
|
||||
|
||||
@@ -643,14 +657,10 @@ static unsigned int sessionChangeHash(
|
||||
int isPK = pTab->abPK[i];
|
||||
if( bPkOnly && isPK==0 ) continue;
|
||||
|
||||
/* It is not possible for eType to be SQLITE_NULL here. The session
|
||||
** module does not record changes for rows with NULL values stored in
|
||||
** primary key columns. */
|
||||
assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
|
||||
|| eType==SQLITE_TEXT || eType==SQLITE_BLOB
|
||||
|| eType==SQLITE_NULL || eType==0
|
||||
);
|
||||
assert( !isPK || (eType!=0 && eType!=SQLITE_NULL) );
|
||||
|
||||
if( isPK ){
|
||||
a++;
|
||||
@@ -658,12 +668,16 @@ static unsigned int sessionChangeHash(
|
||||
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
|
||||
h = sessionHashAppendI64(h, sessionGetI64(a));
|
||||
a += 8;
|
||||
}else{
|
||||
}else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
|
||||
int n;
|
||||
a += sessionVarintGet(a, &n);
|
||||
h = sessionHashAppendBlob(h, n, a);
|
||||
a += n;
|
||||
}
|
||||
/* It should not be possible for eType to be SQLITE_NULL or 0x00 here,
|
||||
** as the session module does not record changes for rows with NULL
|
||||
** values stored in primary key columns. But a corrupt changesets
|
||||
** may contain such a value. */
|
||||
}else{
|
||||
a += sessionSerialLen(a);
|
||||
}
|
||||
@@ -3072,10 +3086,13 @@ static int sessionGenerateChangeset(
|
||||
}
|
||||
|
||||
if( pSession->rc ) return pSession->rc;
|
||||
rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
sqlite3_mutex_enter(sqlite3_db_mutex(db));
|
||||
rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0);
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_mutex_leave(sqlite3_db_mutex(db));
|
||||
return rc;
|
||||
}
|
||||
|
||||
for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){
|
||||
if( pTab->nEntry ){
|
||||
@@ -3558,7 +3575,8 @@ static int sessionReadRecord(
|
||||
u8 *aVal = &pIn->aData[pIn->iNext];
|
||||
if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
|
||||
int nByte;
|
||||
pIn->iNext += sessionVarintGet(aVal, &nByte);
|
||||
int nRem = pIn->nData - pIn->iNext;
|
||||
pIn->iNext += sessionVarintGetSafe(aVal, nRem, &nByte);
|
||||
rc = sessionInputBuffer(pIn, nByte);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( nByte<0 || nByte>pIn->nData-pIn->iNext ){
|
||||
@@ -3611,7 +3629,8 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){
|
||||
|
||||
rc = sessionInputBuffer(pIn, 9);
|
||||
if( rc==SQLITE_OK ){
|
||||
nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &nCol);
|
||||
int nBuf = pIn->nData - pIn->iNext;
|
||||
nRead += sessionVarintGetSafe(&pIn->aData[pIn->iNext], nBuf, &nCol);
|
||||
/* The hard upper limit for the number of columns in an SQLite
|
||||
** database table is, according to sqliteLimit.h, 32676. So
|
||||
** consider any table-header that purports to have more than 65536
|
||||
@@ -3631,8 +3650,15 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){
|
||||
while( (pIn->iNext + nRead)<pIn->nData && pIn->aData[pIn->iNext + nRead] ){
|
||||
nRead++;
|
||||
}
|
||||
|
||||
/* Break out of the loop if if the nul-terminator byte has been found.
|
||||
** Otherwise, read some more input data and keep seeking. If there is
|
||||
** no more input data, consider the changeset corrupt. */
|
||||
if( (pIn->iNext + nRead)<pIn->nData ) break;
|
||||
rc = sessionInputBuffer(pIn, nRead + 100);
|
||||
if( rc==SQLITE_OK && (pIn->iNext + nRead)>=pIn->nData ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
}
|
||||
}
|
||||
*pnByte = nRead+1;
|
||||
return rc;
|
||||
@@ -3764,10 +3790,10 @@ static int sessionChangesetNextOne(
|
||||
memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
|
||||
}
|
||||
|
||||
/* Make sure the buffer contains at least 10 bytes of input data, or all
|
||||
** remaining data if there are less than 10 bytes available. This is
|
||||
** sufficient either for the 'T' or 'P' byte and the varint that follows
|
||||
** it, or for the two single byte values otherwise. */
|
||||
/* Make sure the buffer contains at least 2 bytes of input data, or all
|
||||
** remaining data if there are less than 2 bytes available. This is
|
||||
** sufficient either for the 'T' or 'P' byte that begins a new table,
|
||||
** or for the "op" and "bIndirect" single bytes otherwise. */
|
||||
p->rc = sessionInputBuffer(&p->in, 2);
|
||||
if( p->rc!=SQLITE_OK ) return p->rc;
|
||||
|
||||
@@ -3797,11 +3823,13 @@ static int sessionChangesetNextOne(
|
||||
return (p->rc = SQLITE_CORRUPT_BKPT);
|
||||
}
|
||||
|
||||
p->op = op;
|
||||
p->bIndirect = p->in.aData[p->in.iNext++];
|
||||
if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
|
||||
if( (op!=SQLITE_UPDATE && op!=SQLITE_DELETE && op!=SQLITE_INSERT)
|
||||
|| (p->in.iNext>=p->in.nData)
|
||||
){
|
||||
return (p->rc = SQLITE_CORRUPT_BKPT);
|
||||
}
|
||||
p->op = op;
|
||||
p->bIndirect = p->in.aData[p->in.iNext++];
|
||||
|
||||
if( paRec ){
|
||||
int nVal; /* Number of values to buffer */
|
||||
|
||||
+45
-21
@@ -7,6 +7,8 @@
|
||||
#include <string.h>
|
||||
#include "tclsqlite.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef SQLITE_AMALGAMATION
|
||||
typedef unsigned char u8;
|
||||
#endif
|
||||
@@ -856,6 +858,21 @@ static int testStreamInput(
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** This works like Tcl_GetByteArrayFromObj(), except that it returns a buffer
|
||||
** allocated using malloc() that must be freed by the caller. This is done
|
||||
** because Tcl's buffers are often padded by a few bytes, which prevents
|
||||
** small overreads from being detected when tests are run under asan.
|
||||
*/
|
||||
static void *testGetByteArrayFromObj(Tcl_Obj *p, Tcl_Size *pnByte){
|
||||
Tcl_Size nByte = 0;
|
||||
void *aByte = Tcl_GetByteArrayFromObj(p, &nByte);
|
||||
void *aCopy = malloc(nByte ? (size_t)nByte : 1);
|
||||
memcpy(aCopy, aByte, (size_t)nByte);
|
||||
*pnByte = nByte;
|
||||
return aCopy;
|
||||
}
|
||||
|
||||
|
||||
static int SQLITE_TCLAPI testSqlite3changesetApply(
|
||||
int iVersion,
|
||||
@@ -920,7 +937,7 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
|
||||
return TCL_ERROR;
|
||||
}
|
||||
db = *(sqlite3 **)info.objClientData;
|
||||
pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
|
||||
pChangeset = (void *)testGetByteArrayFromObj(objv[2], &nChangeset);
|
||||
ctx.pConflictScript = objv[3];
|
||||
ctx.pFilterScript = objc==5 ? objv[4] : 0;
|
||||
ctx.interp = interp;
|
||||
@@ -972,6 +989,7 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
|
||||
}
|
||||
}
|
||||
|
||||
free(pChangeset);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return test_session_error(interp, rc, 0);
|
||||
}else{
|
||||
@@ -1195,7 +1213,12 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach(
|
||||
pCS = objv[2];
|
||||
pScript = objv[3];
|
||||
|
||||
pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset);
|
||||
/* Take a copy of the changeset into an exact sized buffer allocated
|
||||
** using malloc(). The Tcl buffer will be padded by a few bytes, which
|
||||
** prevents small overreads from being detected by ASAN when the tests
|
||||
** are run. */
|
||||
pChangeset = (void*)testGetByteArrayFromObj(pCS, &nChangeset);
|
||||
|
||||
sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
|
||||
if( isInvert ){
|
||||
int f = SQLITE_CHANGESETSTART_INVERT;
|
||||
@@ -1216,32 +1239,33 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach(
|
||||
rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr);
|
||||
}
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
return test_session_error(interp, rc, 0);
|
||||
}
|
||||
|
||||
while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
|
||||
Tcl_Obj *pVar = 0; /* Tcl value to set $VARNAME to */
|
||||
pVar = testIterData(pIter);
|
||||
Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
|
||||
rc = Tcl_EvalObjEx(interp, pScript, 0);
|
||||
if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
|
||||
sqlite3changeset_finalize(pIter);
|
||||
return rc==TCL_BREAK ? TCL_OK : rc;
|
||||
if( rc==SQLITE_OK ){
|
||||
while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
|
||||
Tcl_Obj *pVar = 0; /* Tcl value to set $VARNAME to */
|
||||
pVar = testIterData(pIter);
|
||||
Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
|
||||
rc = Tcl_EvalObjEx(interp, pScript, 0);
|
||||
if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
|
||||
sqlite3changeset_finalize(pIter);
|
||||
free(pChangeset);
|
||||
return rc==TCL_BREAK ? TCL_OK : rc;
|
||||
}
|
||||
}
|
||||
|
||||
if( isCheckNext ){
|
||||
int rc2 = sqlite3changeset_next(pIter);
|
||||
rc = sqlite3changeset_finalize(pIter);
|
||||
assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
|
||||
}else{
|
||||
rc = sqlite3changeset_finalize(pIter);
|
||||
}
|
||||
}
|
||||
|
||||
if( isCheckNext ){
|
||||
int rc2 = sqlite3changeset_next(pIter);
|
||||
rc = sqlite3changeset_finalize(pIter);
|
||||
assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
|
||||
}else{
|
||||
rc = sqlite3changeset_finalize(pIter);
|
||||
}
|
||||
free(pChangeset);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return test_session_error(interp, rc, 0);
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
|
||||
+69
-21
@@ -86,6 +86,7 @@ emo.strip = 💈
|
||||
emo.test = 🧪
|
||||
emo.tool = 🔨
|
||||
emo.wasm-opt = 🧼
|
||||
emo.cleanup = 🧼
|
||||
# 👷🪄🧮🧫🧽🍿⛽🚧🎱🪚🏆🧼
|
||||
|
||||
#
|
||||
@@ -205,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...)
|
||||
@@ -317,7 +318,7 @@ endif
|
||||
#
|
||||
sqlite3.canonical.c = $(dir.top)/sqlite3.c
|
||||
sqlite3.c ?= $(firstword $(wildcard $(dir.top)/sqlite3-see.c) $(sqlite3.canonical.c))
|
||||
sqlite3.h = $(dir $(sqlite3.c))/sqlite3.h
|
||||
sqlite3.h = $(dir $(sqlite3.c))sqlite3.h
|
||||
|
||||
#
|
||||
# bin.version-info = binary to output various sqlite3 version info for
|
||||
@@ -565,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))
|
||||
@@ -868,7 +871,7 @@ $(sqlite3-license-version.js): $(bin.version-info) \
|
||||
$(dir.api)/sqlite3-license-version-header.js $(MAKEFILE)
|
||||
@$(call b.mkdir@); echo '$(logtag.@) $(emo.disk)'; { \
|
||||
cat $(dir.api)/sqlite3-license-version-header.js || exit $$?; \
|
||||
echo '/*'; \
|
||||
echo '/* @preserve'; \
|
||||
echo '** This code was built from sqlite3 version...'; \
|
||||
echo '**'; \
|
||||
awk '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' $(sqlite3.h); \
|
||||
@@ -910,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
|
||||
@@ -1181,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.
|
||||
@@ -1220,7 +1225,7 @@ $(foreach B,$(b.names),$(eval post: $(post-js.$(B).js)))
|
||||
# These flags get applied via $(bin.mkwb).
|
||||
emcc.speedtest1.common = $(emcc_opt_full)
|
||||
emcc.speedtest1 = -I. -I$(dir $(sqlite3.canonical.c))
|
||||
emcc.speedtest1 += -sENVIRONMENT=web
|
||||
emcc.speedtest1 += -sENVIRONMENT=web,worker
|
||||
emcc.speedtest1 += -sALLOW_MEMORY_GROWTH
|
||||
emcc.speedtest1 += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.32)
|
||||
emcc.speedtest1.common += -sINVOKE_RUN=0
|
||||
@@ -1263,23 +1268,33 @@ $(EXPORTED_FUNCTIONS.speedtest1): $(EXPORTED_FUNCTIONS.api)
|
||||
@$(call b.mkdir@); $(call b.echo,@,$(emo.disk)); \
|
||||
{ echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api); } > $@ || exit
|
||||
speedtest1: b-speedtest1
|
||||
st: speedtest1
|
||||
|
||||
#
|
||||
# Generate 64-bit variants of speedtest1*.{js,html}
|
||||
#
|
||||
# $1 = input file
|
||||
# $2 = output file
|
||||
#
|
||||
# TODO: preprocess these like we do the rest.
|
||||
#
|
||||
define gen-st64
|
||||
$(2): $(1)
|
||||
@$$(call b.echo,speedtest164,$$(emo.disk)$(emo.lock) Creating from $$<)
|
||||
@rm -f $$@; \
|
||||
sed -e 's/$(3)\.js/$(3)-64bit\.js/' < $$< > $$@; \
|
||||
rm -f $$@; \
|
||||
sed -e 's/speedtest1\.js/speedtest1-64bit\.js/' \
|
||||
-e 's/speedtest1-worker\.js/speedtest1-worker-64bit\.js/' \
|
||||
< $$< > $$@; \
|
||||
chmod -w $$@
|
||||
b-speedtest164: $(2)
|
||||
$(2): b-speedtest164
|
||||
speedtest1: $(1) $(2)
|
||||
CLEAN_FILES += $(2)
|
||||
endef
|
||||
speedtest1: b-speedtest164
|
||||
|
||||
$(eval $(call gen-st64,speedtest1.html,speedtest1-64bit.html,speedtest1))
|
||||
$(eval $(call gen-st64,speedtest1-worker.html,speedtest1-worker-64bit.html,speedtest1-worker))
|
||||
$(eval $(call gen-st64,speedtest1-worker.js,speedtest1-worker-64bit.js,speedtest1-worker))
|
||||
$(eval $(call gen-st64,speedtest1.html,speedtest1-64bit.html))
|
||||
$(eval $(call gen-st64,speedtest1-worker.html,speedtest1-worker-64bit.html))
|
||||
$(eval $(call gen-st64,speedtest1-worker.js,speedtest1-worker-64bit.js))
|
||||
# end speedtest1.js
|
||||
########################################################################
|
||||
|
||||
@@ -1517,9 +1532,9 @@ update-docs:
|
||||
exit 127
|
||||
else
|
||||
wasm.docs.jswasm = $(wasm.docs.home)/jswasm
|
||||
update-docs: $(bin.stripccomments) $(out.sqlite3.js) $(out.sqlite3.wasm)
|
||||
update-docs: $(bin.stripccomments) $(out.vanilla.js) $(out.vanilla.wasm)
|
||||
@echo "Copying files to the /wasm docs. Be sure to use an -Oz build for this!";
|
||||
cp -p $(sqlite3.wasm) $(wasm.docs.jswasm)/.
|
||||
cp -p $(out.vanilla.wasm) $(wasm.docs.jswasm)/.
|
||||
$(bin.stripccomments) -k -k < $(out.vanilla.js) \
|
||||
| sed -e '/^[ \t]*$$/d' > $(wasm.docs.jswasm)/sqlite3.js
|
||||
cp -p demo-123.js demo-123.html demo-123-worker.html $(wasm.docs.home)/.
|
||||
@@ -1582,6 +1597,39 @@ endif
|
||||
# ^^^ making dist/snapshot
|
||||
CLEAN_FILES += $(wildcard sqlite-wasm-*.zip)
|
||||
|
||||
########################################################################
|
||||
# The npm target is specifically for preparing files for the downstream
|
||||
# https://github.com/sqlite/sqlite-wasm (npm) distribution.
|
||||
#
|
||||
# Per agreement with that project's maintainers, these filenames need
|
||||
# to remain stable. To avoid breakage in their deployment process, any
|
||||
# changes (like renaming files) which potentially break their deployment
|
||||
# needs to be communicated to that project via opening a new ticket or
|
||||
# direct coordination with its maintainers.
|
||||
#
|
||||
# This target does a full clean/rebuild so that we can ensure that the
|
||||
# optimization level is set consistently across all files.
|
||||
#
|
||||
npm.bundle.zip = npm-bundle.zip
|
||||
CLEAN_FILES += $(npm.bundle.zip)
|
||||
# Distributables which need to be built for npm:
|
||||
npm_files = $(addprefix $(dir.dout)/, \
|
||||
sqlite3-bundler-friendly.mjs \
|
||||
sqlite3-opfs-async-proxy.js \
|
||||
sqlite3-worker1-bundler-friendly.mjs \
|
||||
sqlite3-worker1-promiser.mjs \
|
||||
sqlite3-worker1.mjs \
|
||||
sqlite3.mjs \
|
||||
sqlite3.wasm \
|
||||
sqlite3-node.mjs \
|
||||
)
|
||||
npm: $(sqlite3.canonical.c)
|
||||
@echo "$(emo.cleanup) Forcing a clean rebuild to ensure consistent optimization flags."
|
||||
$(MAKE) clean
|
||||
$(MAKE) -e "emcc_opt=-Oz $(emcc-opt-extra)" $(npm_files)
|
||||
rm -f $(npm.bundle.zip); zip -r $(npm.bundle.zip) $(npm_files)
|
||||
unzip -l $(npm.bundle.zip)
|
||||
|
||||
########################################################################
|
||||
# Explanation of, and some commentary on, various emcc build flags
|
||||
# follows. Full docs for these can be found at:
|
||||
|
||||
@@ -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,4 @@ _fiddle_reset_db
|
||||
_fiddle_db_handle
|
||||
_fiddle_db_vfs
|
||||
_fiddle_export_db
|
||||
//#endif fiddle
|
||||
//#/if fiddle
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
//#if target:es6-module
|
||||
const toExportForESM =
|
||||
//#endif
|
||||
//#/if
|
||||
(function(){
|
||||
//console.warn("this is extern-post-js");
|
||||
/**
|
||||
@@ -73,6 +73,8 @@ const toExportForESM =
|
||||
|
||||
const sIM = globalThis.sqlite3InitModule = function ff(...args){
|
||||
//console.warn("Using replaced sqlite3InitModule()",globalThis.location);
|
||||
sIMS.emscriptenLocateFile = args[0]?.locateFile /* see pre-js.c-pp.js [tag:locateFile] */;
|
||||
sIMS.emscriptenInstantiateWasm = args[0]?.instantiateWasm /* see pre-js.c-pp.js [tag:locateFile] */;
|
||||
return originalInit(...args).then((EmscriptenModule)=>{
|
||||
sIMS.debugModule("sqlite3InitModule() sIMS =",sIMS);
|
||||
sIMS.debugModule("sqlite3InitModule() EmscriptenModule =",EmscriptenModule);
|
||||
@@ -95,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);
|
||||
@@ -126,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
|
||||
/**
|
||||
@@ -27,10 +27,14 @@
|
||||
|
||||
This SQLite JS build configuration is entirely unsupported! It has
|
||||
not been tested beyond the ability to compile it. It may not
|
||||
load. It may not work properly. Only builds targeting browser
|
||||
environments are supported and tested.
|
||||
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 and any node builds) are not supported
|
||||
deliverables.
|
||||
*/
|
||||
//#endif
|
||||
//#/if
|
||||
//#if not target:es6-bundler-friendly
|
||||
(function(Module){
|
||||
const sIMS =
|
||||
globalThis.sqlite3InitModuleState/*from extern-post-js.c-pp.js*/
|
||||
@@ -68,6 +72,14 @@
|
||||
approach.
|
||||
*/
|
||||
Module['locateFile'] = function(path, prefix) {
|
||||
if( this.emscriptenLocateFile instanceof Function ){
|
||||
/* [tag:locateFile] Client-overridden impl. We do not support
|
||||
this but offer it as a back-door which will go away the
|
||||
moment either Emscripten changes that interface or we manage
|
||||
to get non-Emscripten builds working.
|
||||
https://sqlite.org/forum/forumpost/1eec339854c935bd */
|
||||
return this.emscriptenLocateFile(path, prefix);
|
||||
}
|
||||
//#if target:es6-module
|
||||
return new URL(path, import.meta.url).href;
|
||||
//#else
|
||||
@@ -90,10 +102,10 @@
|
||||
"result =", theFile
|
||||
);
|
||||
return theFile;
|
||||
//#endif target:es6-module
|
||||
//#/if target:es6-module
|
||||
}.bind(sIMS);
|
||||
|
||||
//#if Module.instantiateWasm and not wasmfs
|
||||
//#if Module.instantiateWasm and not wasmfs and not target:node
|
||||
/**
|
||||
Override Module.instantiateWasm().
|
||||
|
||||
@@ -102,8 +114,15 @@
|
||||
https://github.com/emscripten-core/emscripten/issues/17951
|
||||
|
||||
In such builds we must disable this.
|
||||
|
||||
It's disabled in the (unsupported/untested) node builds because
|
||||
node does not do fetch().
|
||||
*/
|
||||
Module['instantiateWasm'] = function callee(imports,onSuccess){
|
||||
if( this.emscriptenInstantiateWasm instanceof Function ){
|
||||
/* See [tag:locateFile]. Same story here */
|
||||
return this.emscriptenInstantiateWasm(imports, onSuccess);
|
||||
}
|
||||
const sims = this;
|
||||
const uri = Module.locateFile(
|
||||
sims.wasmFilename, (
|
||||
@@ -129,6 +148,7 @@
|
||||
.then(finalThen)
|
||||
return loadWasm();
|
||||
}.bind(sIMS);
|
||||
//#endif Module.instantiateWasm and not wasmfs
|
||||
//#/if Module.instantiateWasm and not wasmfs
|
||||
})(Module);
|
||||
//#/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() */
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,20 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
the sqlite3 binding if, e.g., the wrapper is in the main thread
|
||||
and the sqlite3 API is in a worker. */
|
||||
|
||||
const outWrapper = function(f){
|
||||
return (...args)=>f("sqlite3.oo1:",...args);
|
||||
};
|
||||
|
||||
const debug = sqlite3.__isUnderTest
|
||||
? outWrapper(console.debug.bind(console))
|
||||
: outWrapper(sqlite3.config.debug);
|
||||
const warn = sqlite3.__isUnderTest
|
||||
? outWrapper(console.warn.bind(console))
|
||||
: outWrapper(sqlite3.config.warn);
|
||||
const error = sqlite3.__isUnderTest
|
||||
? outWrapper(console.error.bind(console))
|
||||
: outWrapper(sqlite3.config.error);
|
||||
|
||||
/**
|
||||
In order to keep clients from manipulating, perhaps
|
||||
inadvertently, the underlying pointer values of DB and Stmt
|
||||
@@ -201,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
|
||||
@@ -214,13 +228,16 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
properties:
|
||||
|
||||
- `.filename`: the db filename. It may be a special name like ":memory:"
|
||||
or "".
|
||||
or "". It may also be a URI-style name.
|
||||
|
||||
- `.flags`: as documented in the DB constructor.
|
||||
|
||||
- `.vfs`: as documented in the DB constructor.
|
||||
|
||||
It also accepts those as the first 3 arguments.
|
||||
|
||||
In non-default builds it may accept additional configuration
|
||||
options.
|
||||
*/
|
||||
const dbCtorHelper = function ctor(...args){
|
||||
const opt = ctor.normalizeArgs(...args);
|
||||
@@ -284,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.");
|
||||
@@ -436,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
|
||||
@@ -785,6 +802,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
sqlite3_db_filename() value for the given database name,
|
||||
defaulting to "main". The argument may be either a JS string
|
||||
or a pointer to a WASM-allocated C-string.
|
||||
|
||||
this.filename may be in the form of a URI-style string, whereas
|
||||
the returned string contains only the filename part.
|
||||
*/
|
||||
dbFilename: function(dbName='main'){
|
||||
return capi.sqlite3_db_filename(affirmDbOpen(this).pointer, dbName);
|
||||
@@ -906,15 +926,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
result set, but only if that statement has any result rows. The
|
||||
callback's "this" is the options object, noting that this
|
||||
function synthesizes one if the caller does not pass one to
|
||||
exec(). The second argument passed to the callback is always
|
||||
the current Stmt object, as it's needed if the caller wants to
|
||||
fetch the column names or some such (noting that they could
|
||||
also be fetched via `this.columnNames`, if the client provides
|
||||
the `columnNames` option). If the callback returns a literal
|
||||
`false` (as opposed to any other falsy value, e.g. an implicit
|
||||
`undefined` return), any ongoing statement-`step()` iteration
|
||||
stops without an error. The return value of the callback is
|
||||
otherwise ignored.
|
||||
exec(). The first argument passed to the callback is described
|
||||
below. The second argument is always the current Stmt object,
|
||||
as it's needed if the caller wants to fetch the column names or
|
||||
some such (noting that they could also be fetched via
|
||||
`this.columnNames`, if the client provides the `columnNames`
|
||||
option). If the callback returns a literal `false` (as opposed
|
||||
to any other falsy value, e.g. an implicit `undefined` return),
|
||||
any ongoing statement-`step()` iteration stops without an
|
||||
error. The return value of the callback is otherwise ignored.
|
||||
|
||||
ACHTUNG: The callback MUST NOT modify the Stmt object. Calling
|
||||
any of the Stmt.get() variants, Stmt.getColumnName(), or
|
||||
@@ -947,20 +967,23 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
A.3) `'stmt'` causes the current Stmt to be passed to the
|
||||
callback, but this mode will trigger an exception if
|
||||
`resultRows` is an array because appending the transient
|
||||
statement to the array would be downright unhelpful.
|
||||
statement to the array would be downright unhelpful. This
|
||||
option is a legacy feature, retained for backwards
|
||||
compatibility. The statement object is passed as the second
|
||||
argument to the callback, as described above.
|
||||
|
||||
B) An integer, indicating a zero-based column in the result
|
||||
row. Only that one single value will be passed on.
|
||||
row. Only that one single value, in JS form, will be passed on.
|
||||
|
||||
C) A string with a minimum length of 2 and leading character of
|
||||
'$' will fetch the row as an object, extract that one field,
|
||||
and pass that field's value to the callback. Note that these
|
||||
keys are case-sensitive so must match the case used in the
|
||||
and pass that field's value to the callback. These keys are
|
||||
case-sensitive so must match the case used in the
|
||||
SQL. e.g. `"select a A from t"` with a `rowMode` of `'$A'`
|
||||
would work but `'$a'` would not. A reference to a column not in
|
||||
the result set will trigger an exception on the first row (as
|
||||
the check is not performed until rows are fetched). Note also
|
||||
that `$` is a legal identifier character in JS so need not be
|
||||
the check is not performed until rows are fetched). Note that
|
||||
`$` is a legal identifier character in JS so need not be
|
||||
quoted.
|
||||
|
||||
Any other `rowMode` value triggers an exception.
|
||||
@@ -1000,6 +1023,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
- `callback` and `resultRows`: permit an array entries with
|
||||
semantics similar to those described for `bind` above.
|
||||
|
||||
OTOH, this function already does too much.
|
||||
*/
|
||||
exec: function(/*(sql [,obj]) || (obj)*/){
|
||||
affirmDbOpen(this);
|
||||
@@ -1023,7 +1047,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
/* Optimization: if the SQL is a TypedArray we can save some string
|
||||
conversion costs. */;
|
||||
/* Allocate the two output pointers (ppStmt, pzTail) and heap
|
||||
space for the SQL (pSql). When prepare_v2() returns, pzTail
|
||||
space for the SQL (pSql). When prepare_v3() returns, pzTail
|
||||
will point to somewhere in pSql. */
|
||||
let sqlByteLen = isTA ? arg.sql.byteLength : wasm.jstrlen(arg.sql);
|
||||
const ppStmt = wasm.scopedAlloc(
|
||||
@@ -1035,8 +1059,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
const pSqlEnd = wasm.ptr.add(pSql, sqlByteLen);
|
||||
if(isTA) wasm.heap8().set(arg.sql, pSql);
|
||||
else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false);
|
||||
wasm.poke(wasm.ptr.add(pSql, sqlByteLen), 0/*NUL terminator*/);
|
||||
while(pSql && wasm.peek(pSql, 'i8')
|
||||
wasm.poke8(wasm.ptr.add(pSql, sqlByteLen), 0/*NUL terminator*/);
|
||||
while(pSql && wasm.peek8(pSql)
|
||||
/* Maintenance reminder:^^^ _must_ be 'i8' or else we
|
||||
will very likely cause an endless loop. What that's
|
||||
doing is checking for a terminating NUL byte. If we
|
||||
@@ -1100,6 +1124,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
/* In order to trigger an exception in the
|
||||
INSERT...RETURNING locking scenario:
|
||||
https://sqlite.org/forum/forumpost/36f7a2e7494897df
|
||||
[tag:insert-returning-reset]
|
||||
*/).finalize();
|
||||
stmt = null;
|
||||
}/*prepare() loop*/
|
||||
@@ -1116,6 +1141,132 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
return arg.returnVal();
|
||||
}/*exec()*/,
|
||||
|
||||
//#if 0
|
||||
/**
|
||||
Experimental and untested - do not use.
|
||||
|
||||
Prepares one or more SQL statements, passing each to a callback
|
||||
for processing.
|
||||
|
||||
It requires an options object with the following properties:
|
||||
|
||||
- "sql": SQL in any format accepted by exec().
|
||||
|
||||
- "callback" (function): gets passed each prepared statement,
|
||||
as described below.
|
||||
|
||||
- "asPointer" (bool=false): if true, the callback is passed the
|
||||
WASM (sqlite3*) pointer instead of a Stmt object.
|
||||
|
||||
- "saveSql" (array): if set, the SQL of each prepared statement
|
||||
is appended to this array. This can be used without a callback
|
||||
to split SQL into its component statements. Purely empty
|
||||
statements (for for which sqlite3_prepare() returns a NULL
|
||||
sqlite3_stmt, i.e. spaces and comments) are not added to this
|
||||
list unless...
|
||||
|
||||
- "saveEmpty" (bool=false): If true, empty statements are
|
||||
retained in opt.saveSql, but their leading/trailing whitespace
|
||||
is trimmed (as for queries) so they may be empty.
|
||||
|
||||
For each statement in the input SQL:
|
||||
|
||||
1) If opt.saveSql is set, the SQL is appended to it.
|
||||
|
||||
2) If callback is set, callback(S) is called, where S is either
|
||||
a Stmt object (by default) or an (sqlite3*) WASM pointer (if
|
||||
opt.asPointer is true). If the callback returns a literal true
|
||||
(as opposed to any other truthy value), ownership of S is
|
||||
transferred to the callback, otherwise S is reset and finalized
|
||||
as soon as the callback returns. If the callback throws, S is
|
||||
unconditionally finalized.
|
||||
|
||||
If neither of opt.saveSql nor opt.callback are set, this
|
||||
function does nothing more than prepare and finalize each
|
||||
statement, which will trigger an exception if any of them
|
||||
contain invalid SQL.
|
||||
*/
|
||||
forEachStmt: function(opt){
|
||||
affirmDbOpen(this);
|
||||
opt ??= Object.create(null);
|
||||
if(!opt.sql){
|
||||
return toss3("exec() requires an SQL string.");
|
||||
}
|
||||
const sql = util.flexibleString(opt.sql);
|
||||
const callback = opt.callback;
|
||||
let stmt, pStmt;
|
||||
const stack = wasm.scopedAllocPush();
|
||||
const saveSql = Array.isArray(opt.saveSql) ? opt.saveSql : undefined;
|
||||
try{
|
||||
const isTA = util.isSQLableTypedArray(opt.sql)
|
||||
/* Optimization: if the SQL is a TypedArray we can save some string
|
||||
conversion costs. */;
|
||||
/* Allocate the two output pointers (ppStmt, pzTail) and heap
|
||||
space for the SQL (pSql). When prepare_v3() returns, pzTail
|
||||
will point to somewhere in pSql. */
|
||||
let sqlByteLen = isTA ? opt.sql.byteLength : wasm.jstrlen(sql);
|
||||
const ppStmt = wasm.scopedAlloc(
|
||||
/* output (sqlite3_stmt**) arg and pzTail */
|
||||
(2 * wasm.ptr.size) + (sqlByteLen + 1/* SQL + NUL */)
|
||||
);
|
||||
const pzTail = wasm.ptr.add(ppStmt, wasm.ptr.size) /* final arg to sqlite3_prepare_v2() */;
|
||||
let pSql = wasm.ptr.add(pzTail, wasm.ptr.size) /* start of the SQL string */;
|
||||
const pSqlEnd = wasm.ptr.add(pSql, sqlByteLen);
|
||||
if(isTA) wasm.heap8().set(sql, pSql);
|
||||
else wasm.jstrcpy(sql, wasm.heap8(), pSql, sqlByteLen, false);
|
||||
wasm.poke8(wasm.ptr.add(pSql, sqlByteLen), 0/*NUL terminator*/);
|
||||
while( pSql && wasm.peek8(pSql) ){
|
||||
pStmt = stmt = null;
|
||||
wasm.pokePtr([ppStmt, pzTail], 0);
|
||||
const zHead = pSql;
|
||||
DB.checkRc(this, capi.sqlite3_prepare_v3(
|
||||
this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail
|
||||
));
|
||||
[pStmt, pSql] = wasm.peekPtr([ppStmt, pzTail]);
|
||||
sqlByteLen = wasm.ptr.addn(pSqlEnd,-pSql);
|
||||
if(opt.saveSql){
|
||||
if( pStmt ) opt.saveSql.push(capi.sqlite3_sql(pStmt).trim());
|
||||
else if( opt.saveEmpty ){
|
||||
saveSql.push(wasm.typedArrayToString(
|
||||
wasm.heap8u(), Number(zHead),
|
||||
wasm.ptr.addn(zHead, sqlByteLen)
|
||||
).trim(/*arguable*/));
|
||||
}
|
||||
}
|
||||
if(!pStmt) continue;
|
||||
//sqlite3.config.debug("forEachStmt() pSql =",capi.sqlite3_sql(pStmt));
|
||||
if( !opt.callback ){
|
||||
capi.sqlite3_finalize(pStmt);
|
||||
pStmt = null;
|
||||
continue;
|
||||
}
|
||||
stmt = opt.asPointer ? null : new Stmt(this, pStmt, BindTypes);
|
||||
if( true===callaback(stmt || pStmt) ){
|
||||
stmt = pStmt = null /*callback took ownership */;
|
||||
}else if(stmt){
|
||||
pStmt = null;
|
||||
stmt.reset(
|
||||
/* See [tag:insert-returning-reset]. The thinking here is
|
||||
that if the callback didn't throw for this, it
|
||||
probably should have.
|
||||
*/).finalize();
|
||||
stmt = null;
|
||||
}else{
|
||||
const rx = capi.sqlite3_reset(pStmt/*[tag:insert-returning-reset]*/);
|
||||
capi.sqlite3_finalize(pStmt);
|
||||
pStmt = null;
|
||||
DB.checkRc(this, rx);
|
||||
}
|
||||
}/*prepare() loop*/
|
||||
}finally{
|
||||
if(stmt) stmt.finalize();
|
||||
else if(pStmt) capi.sqlite3_finalize(pStmt);
|
||||
wasm.scopedAllocPop(stack);
|
||||
}
|
||||
return this;
|
||||
}/*forEachStmt()*/,
|
||||
//#/if nope
|
||||
|
||||
/**
|
||||
Creates a new UDF (User-Defined Function) which is accessible
|
||||
via SQL code. This function may be called in any of the
|
||||
@@ -2275,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,12 @@ 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);
|
||||
}
|
||||
},
|
||||
/**
|
||||
Given a byte array or ArrayBuffer, this function throws if the
|
||||
lead bytes of that buffer do not hold a SQLite3 database header,
|
||||
@@ -887,7 +901,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
|
||||
Like this.alloc.impl(), this.realloc.impl() is a direct binding
|
||||
to the underlying realloc() implementation which does not throw
|
||||
exceptions, instead returning 0 on allocation error.
|
||||
exceptions, instead returning 0 (or 0n) on allocation error.
|
||||
*/
|
||||
realloc: undefined/*installed later*/,
|
||||
|
||||
@@ -1003,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);
|
||||
@@ -1011,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);
|
||||
});
|
||||
@@ -1062,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
|
||||
@@ -1284,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
|
||||
@@ -1408,7 +1422,7 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
or not provided, then "main" is assumed.
|
||||
*/
|
||||
capi.sqlite3_js_db_vfs =
|
||||
(dbPointer, dbName=0)=>util.sqlite3__wasm_db_vfs(dbPointer, dbName);
|
||||
(dbPointer, dbName=wasm.ptr.null)=>util.sqlite3__wasm_db_vfs(dbPointer, dbName);
|
||||
|
||||
/**
|
||||
A thin wrapper around capi.sqlite3_aggregate_context() which
|
||||
@@ -1638,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', '*']);
|
||||
@@ -1659,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
|
||||
@@ -1893,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
|
||||
@@ -1933,6 +1948,62 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
'sqlite3changeset_old');
|
||||
}/*changeset/preupdate additions*/
|
||||
|
||||
/**
|
||||
EXPERIMENTAL. For tentative addition in 3.53.0.
|
||||
|
||||
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 negative maxTimes value,
|
||||
with the caveat that there is no recovery from that unless the
|
||||
beforeRetry() can figure out when to throw.
|
||||
|
||||
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,
|
||||
@@ -1951,7 +2022,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
|
||||
@@ -1997,6 +2068,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;
|
||||
};
|
||||
@@ -2016,19 +2088,24 @@ 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 */ ){
|
||||
sqlite3.__isUnderTest = !!sqlite3IsUnderTest;
|
||||
}
|
||||
try{
|
||||
sqlite3ApiBootstrap.initializers.forEach((f)=>{
|
||||
f(sqlite3);
|
||||
@@ -2051,9 +2128,6 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
builds, this would need to be injected in somewhere to get
|
||||
that VFS loading. */ = sqlite3InitScriptInfo;
|
||||
}
|
||||
sqlite3.__isUnderTest =
|
||||
('undefined'===typeof sqlite3IsUnderTest /* from post-js-header.js */)
|
||||
? false : !!sqlite3IsUnderTest;
|
||||
if( sqlite3.__isUnderTest ){
|
||||
if( 'undefined'!==typeof EmscriptenModule ){
|
||||
sqlite3.config.emscripten = EmscriptenModule;
|
||||
@@ -2066,10 +2140,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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
/* @preserve
|
||||
**
|
||||
** LICENSE for the sqlite3 WebAssembly/JavaScript APIs.
|
||||
**
|
||||
** This bundle (typically released as sqlite3.js or sqlite3.mjs)
|
||||
|
||||
+345
-192
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/* @preserve
|
||||
2022-09-16
|
||||
|
||||
The author disclaims copyright to this source code. In place of a
|
||||
@@ -46,10 +46,22 @@
|
||||
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".
|
||||
*/
|
||||
"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 +78,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 +101,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 +116,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 +291,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 +310,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 +368,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 +500,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 +563,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 +590,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) => {
|
||||
//error("xLock() initial promise entered...");
|
||||
navigator.locks.request(lockName, { mode: requestedMode }, async (lock) => {
|
||||
//error("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;
|
||||
}
|
||||
//error("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 +789,112 @@ 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);
|
||||
//#if 0
|
||||
if( isWebLocker && (opId==opIds.xLock || opIds==opIds.xUnlock) ){
|
||||
/* An expert suggests that this introduces a race condition,
|
||||
but my eyes aren't seeing it. The hope was that this
|
||||
would improve the lock speed a tick, but it does not
|
||||
appear to. */
|
||||
hnd(...args);
|
||||
continue;
|
||||
}
|
||||
//#/if
|
||||
await hnd(...args);
|
||||
}catch(e){
|
||||
error('in waitLoop():',e);
|
||||
error('in waitLoop():', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -756,6 +902,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 +918,7 @@ const installAsyncProxy = function(){
|
||||
}
|
||||
});
|
||||
initS11n();
|
||||
//warn("verbosity =",opt.verbose, state.verbose);
|
||||
log("init state",state);
|
||||
wPost('opfs-async-inited');
|
||||
waitLoop();
|
||||
@@ -782,22 +930,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{
|
||||
+2007
-238
File diff suppressed because it is too large
Load Diff
@@ -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 */;
|
||||
@@ -410,7 +414,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
}/*vfsMethods*/;
|
||||
|
||||
/**
|
||||
Creates and initializes an sqlite3_vfs instance for an
|
||||
Creates, initializes, and returns an sqlite3_vfs instance for an
|
||||
OpfsSAHPool. The argument is the VFS's name (JS string).
|
||||
|
||||
Throws if the VFS name is already registered or if something
|
||||
@@ -1157,8 +1161,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
described at the end of these docs.
|
||||
|
||||
This function accepts an options object to configure certain
|
||||
parts but it is only acknowledged for the very first call and
|
||||
ignored for all subsequent calls.
|
||||
parts but it is only acknowledged for the very first call for
|
||||
each distinct name and ignored for all subsequent calls with that
|
||||
same name.
|
||||
|
||||
The options, in alphabetical order:
|
||||
|
||||
@@ -1224,7 +1229,14 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
- Paths given to it _must_ be absolute. Relative paths will not
|
||||
be properly recognized. This is arguably a bug but correcting it
|
||||
requires some hoop-jumping in routines which have no business
|
||||
doing such tricks.
|
||||
doing such tricks. (2026-01-19 (2.5 years later): the specifics
|
||||
are lost to history, but this was a side effect of xOpen()
|
||||
receiving an immutable C-string filename, to which no implicit
|
||||
"/" can be prefixed without causing a discrepancy between what
|
||||
the user provided and what the VFS stores. Its conceivable that
|
||||
that quirk could be glossed over in xFullPathname(), but
|
||||
regressions when doing so cannot be ruled out, so there are no
|
||||
current plans to change this behavior.)
|
||||
|
||||
- It is possible to install multiple instances under different
|
||||
names, each sandboxed from one another inside their own private
|
||||
@@ -1459,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,133 @@
|
||||
//#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
|
||||
custom Atomics.wait()/notify() protocol. This file holds the
|
||||
"synchronous half" of the VFS, whereas it shares the "asynchronous
|
||||
half" of the "opfs" VFS.
|
||||
|
||||
This file is intended to be appended to the main sqlite3 JS
|
||||
deliverable somewhere after sqlite3-api-oo1.js.
|
||||
|
||||
TODOs (2026-03-03):
|
||||
|
||||
- For purposes of tester1.js we need to figure out which of these
|
||||
VFSes will install the (internal-use-only) sqlite3.opfs utility code
|
||||
namespace. We need that in order to clean up OPFS files during test
|
||||
runs. Alternately, move those into their own
|
||||
sqlite3ApiBootstrap.initializers entry which precedes both of the
|
||||
VFSes, so they'll have access to it during bootstrapping. The
|
||||
sqlite3.opfs namespace is removed at the end of bootstrapping unless
|
||||
the library is told to run in testing mode (which is not a
|
||||
documented feature).
|
||||
*/
|
||||
'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
@@ -172,10 +172,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
Works like unget() plus it calls dispose() on the
|
||||
StructType object.
|
||||
*/
|
||||
dispose: (pCObj)=>{
|
||||
const o = __xWrap(pCObj,true);
|
||||
if(o) o.dispose();
|
||||
}
|
||||
dispose: (pCObj)=>__xWrap(pCObj,true)?.dispose?.()
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+140
-141
@@ -229,15 +229,18 @@
|
||||
** not by client code, so an argument can be made for reducing their
|
||||
** visibility by not including them in any build-time export lists.
|
||||
**
|
||||
** 2022-09-11: it's not yet _proven_ that this approach works in
|
||||
** non-Emscripten builds. If not, such builds will need to export
|
||||
** those using the --export=... wasm-ld flag (or equivalent). As of
|
||||
** this writing we are tied to Emscripten for various reasons
|
||||
** and cannot test the library with other build environments.
|
||||
** 2025-12-01: for use in non-Emscripten builds, we need a more
|
||||
** invasive macro which explicitly names the export:
|
||||
** SQLITE_WASM_EXPORT2.
|
||||
*/
|
||||
#define SQLITE_WASM_EXPORT __attribute__((used,visibility("default")))
|
||||
// See also:
|
||||
//__attribute__((export_name("theExportedName"), used, visibility("default")))
|
||||
#define SQLITE_WASM_EXPORT_NAMED(X) __attribute__((export_name(#X),used,visibility("default")))
|
||||
#define SQLITE_WASM_EXPORT2(RETTYPE,NAME,SIG) SQLITE_WASM_EXPORT_NAMED(NAME) RETTYPE NAME SIG
|
||||
|
||||
#if 1
|
||||
/** Increase the kvvfs key size limit from 32. */
|
||||
#define KVRECORD_KEY_SZ 128
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Which sqlite3.c we're using needs to be configurable to enable
|
||||
@@ -261,45 +264,6 @@
|
||||
#undef INC__STRINGIFY
|
||||
#undef SQLITE_C
|
||||
|
||||
#if 0
|
||||
/*
|
||||
** An EXPERIMENT in implementing a stack-based allocator analog to
|
||||
** Emscripten's stackSave(), stackAlloc(), stackRestore().
|
||||
** Unfortunately, this cannot work together with Emscripten because
|
||||
** Emscripten defines its own native one and we'd stomp on each
|
||||
** other's memory. Other than that complication, basic tests show it
|
||||
** to work just fine.
|
||||
**
|
||||
** Another option is to malloc() a chunk of our own and call that our
|
||||
** "stack".
|
||||
*/
|
||||
SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_end(void){
|
||||
extern void __heap_base
|
||||
/* see https://stackoverflow.com/questions/10038964 */;
|
||||
return &__heap_base;
|
||||
}
|
||||
SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_begin(void){
|
||||
extern void __data_end;
|
||||
return &__data_end;
|
||||
}
|
||||
static void * pWasmStackPtr = 0;
|
||||
SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_ptr(void){
|
||||
if(!pWasmStackPtr) pWasmStackPtr = sqlite3__wasm_stack_end();
|
||||
return pWasmStackPtr;
|
||||
}
|
||||
SQLITE_WASM_EXPORT void sqlite3__wasm_stack_restore(void * p){
|
||||
pWasmStackPtr = p;
|
||||
}
|
||||
SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_alloc(int n){
|
||||
if(n<=0) return 0;
|
||||
n = (n + 7) & ~7 /* align to 8-byte boundary */;
|
||||
unsigned char * const p = (unsigned char *)sqlite3__wasm_stack_ptr();
|
||||
unsigned const char * const b = (unsigned const char *)sqlite3__wasm_stack_begin();
|
||||
if(b + n >= p || b + n < b/*overflow*/) return 0;
|
||||
return pWasmStackPtr = p - n;
|
||||
}
|
||||
#endif /* stack allocator experiment */
|
||||
|
||||
/*
|
||||
** State for the "pseudo-stack" allocator implemented in
|
||||
** sqlite3__wasm_pstack_xyz(). In order to avoid colliding with
|
||||
@@ -348,10 +312,10 @@ SQLITE_WASM_EXPORT void sqlite3__wasm_pstack_restore(unsigned char * p){
|
||||
** the memory on success, 0 on error (including a negative n value). n
|
||||
** is always adjusted to be a multiple of 8 and returned memory is
|
||||
** always zeroed out before returning (because this keeps the client
|
||||
** JS code from having to do so, and most uses of the pstack will
|
||||
** call for doing so).
|
||||
** JS code from having to do so, and most uses of the pstack call for
|
||||
** doing so).
|
||||
*/
|
||||
SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_alloc(int n){
|
||||
SQLITE_WASM_EXPORT2(void *,sqlite3__wasm_pstack_alloc,(int n)){
|
||||
if( n<=0 ) return 0;
|
||||
n = (n + 7) & ~7 /* align to 8-byte boundary */;
|
||||
if( PStack.pBegin + n > PStack.pPos /*not enough space left*/
|
||||
@@ -363,7 +327,7 @@ SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_alloc(int n){
|
||||
** Return the number of bytes left which can be
|
||||
** sqlite3__wasm_pstack_alloc()'d.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_remaining(void){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_pstack_remaining,(void)){
|
||||
assert(PStack.pPos >= PStack.pBegin);
|
||||
assert(PStack.pPos <= PStack.pEnd);
|
||||
return (int)(PStack.pPos - PStack.pBegin);
|
||||
@@ -374,7 +338,7 @@ SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_remaining(void){
|
||||
** any space which is currently allocated. This value is a
|
||||
** compile-time constant.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_quota(void){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_pstack_quota,(void)){
|
||||
return (int)(PStack.pEnd - PStack.pBegin);
|
||||
}
|
||||
|
||||
@@ -387,8 +351,7 @@ struct WasmTestStruct {
|
||||
void (*xFunc)(void*);
|
||||
};
|
||||
typedef struct WasmTestStruct WasmTestStruct;
|
||||
SQLITE_WASM_EXPORT
|
||||
void sqlite3__wasm_test_struct(WasmTestStruct * s){
|
||||
SQLITE_WASM_EXPORT2(void,sqlite3__wasm_test_struct,(WasmTestStruct * s)){
|
||||
if(s){
|
||||
if( 0 ){
|
||||
/* Do not be alarmed by the small (and odd) pointer values.
|
||||
@@ -428,8 +391,7 @@ void sqlite3__wasm_test_struct(WasmTestStruct * s){
|
||||
** fails to compile with "tables may not be 64-bit" but does not tell
|
||||
** us where it's happening.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
const char * sqlite3__wasm_enum_json(void){
|
||||
SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_enum_json,(void)){
|
||||
static char aBuffer[1024 * 20] =
|
||||
{0} /* where the JSON goes. 2025-09-19: output size=19295, but
|
||||
that can vary slightly from build to build, so a little
|
||||
@@ -609,6 +571,7 @@ 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){
|
||||
@@ -632,6 +595,7 @@ const char * sqlite3__wasm_enum_json(void){
|
||||
DefGroup(encodings) {
|
||||
/* Noting that the wasm binding only aims to support UTF-8. */
|
||||
DefInt(SQLITE_UTF8);
|
||||
DefInt(SQLITE_UTF8_ZT);
|
||||
DefInt(SQLITE_UTF16LE);
|
||||
DefInt(SQLITE_UTF16BE);
|
||||
DefInt(SQLITE_UTF16);
|
||||
@@ -735,6 +699,8 @@ 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) {
|
||||
@@ -768,6 +734,7 @@ 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) {
|
||||
@@ -1019,13 +986,15 @@ const char * sqlite3__wasm_enum_json(void){
|
||||
/** ^^^ indirection needed to expand CurrentStruct */
|
||||
#define StructBinder StructBinder_(CurrentStruct)
|
||||
#define _StructBinder CloseBrace(2)
|
||||
#define M(MEMBER,SIG) \
|
||||
outf("%s\"%s\": " \
|
||||
"{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \
|
||||
(n++ ? ", " : ""), #MEMBER, \
|
||||
(int)offsetof(CurrentStruct,MEMBER), \
|
||||
(int)sizeof(((CurrentStruct*)0)->MEMBER), \
|
||||
SIG)
|
||||
#define M3(MEMBER,SIG,READONLY) \
|
||||
outf("%s\"%s\": " \
|
||||
"{\"offset\":%d,\"sizeof\":%d,\"signature\":\"%s\"%s}", \
|
||||
(n++ ? ", " : ""), #MEMBER, \
|
||||
(int)offsetof(CurrentStruct,MEMBER), \
|
||||
(int)sizeof(((CurrentStruct*)0)->MEMBER), \
|
||||
SIG, (READONLY ? ",\"readOnly\":true" : ""))
|
||||
#define M(MEMBER,SIG) M3(MEMBER,SIG,0)
|
||||
#define MRO(MEMBER,SIG) M3(MEMBER,SIG,1)
|
||||
|
||||
nStruct = 0;
|
||||
out(", \"structs\": ["); {
|
||||
@@ -1088,11 +1057,30 @@ const char * sqlite3__wasm_enum_json(void){
|
||||
#undef CurrentStruct
|
||||
|
||||
#define CurrentStruct sqlite3_kvvfs_methods
|
||||
/* From os_kv.c */
|
||||
StructBinder {
|
||||
M(xRead, "i(sspi)");
|
||||
M(xWrite, "i(sss)");
|
||||
M(xDelete, "i(ss)");
|
||||
M(nKeySize, "i");
|
||||
M(xRcrdRead, "i(sspi)");
|
||||
M(xRcrdWrite, "i(sss)");
|
||||
M(xRcrdDelete, "i(ss)");
|
||||
MRO(nKeySize, "i");
|
||||
MRO(nBufferSize, "i");
|
||||
M(pVfs, "p");
|
||||
M(pIoDb, "p");
|
||||
M(pIoJrnl, "p");
|
||||
} _StructBinder;
|
||||
#undef CurrentStruct
|
||||
|
||||
#define CurrentStruct KVVfsFile
|
||||
/* From os_kv.c */
|
||||
StructBinder {
|
||||
M(base, "p")/*sqlite3_file base*/;
|
||||
M(zClass, "s");
|
||||
M(isJournal, "i");
|
||||
M(nJrnl, "i")/*actually unsigned!*/;
|
||||
M(aJrnl, "p");
|
||||
M(szPage, "i");
|
||||
M(szDb, "j");
|
||||
M(aData, "p");
|
||||
} _StructBinder;
|
||||
#undef CurrentStruct
|
||||
|
||||
@@ -1149,7 +1137,13 @@ const char * sqlite3__wasm_enum_json(void){
|
||||
** sqlite3_index_info, we have to uplift those into constructs we
|
||||
** can access by type name. These structs _must_ match their
|
||||
** in-sqlite3_index_info counterparts byte for byte.
|
||||
*/
|
||||
**
|
||||
** 2025-11-21: this uplifing is no longer necessary, as Jaccwabyt
|
||||
** can now handle nested structs, but "it ain't broke" so there's
|
||||
** no pressing need to rewire this. Also, it's conceivable that
|
||||
** rewiring it might break downstream vtab impls, so it shouldn't
|
||||
** be rewired.
|
||||
*/
|
||||
typedef struct {
|
||||
int iColumn;
|
||||
unsigned char op;
|
||||
@@ -1244,6 +1238,8 @@ const char * sqlite3__wasm_enum_json(void){
|
||||
#undef StructBinder_
|
||||
#undef StructBinder__
|
||||
#undef M
|
||||
#undef MRO
|
||||
#undef M3
|
||||
#undef _StructBinder
|
||||
#undef CloseBrace
|
||||
#undef out
|
||||
@@ -1261,8 +1257,7 @@ const char * sqlite3__wasm_enum_json(void){
|
||||
** method, SQLITE_MISUSE is returned, else the result of the xDelete()
|
||||
** call is returned.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_vfs_unlink,(sqlite3_vfs *pVfs, const char *zName)){
|
||||
int rc = SQLITE_MISUSE /* ??? */;
|
||||
if( 0==pVfs && 0!=zName ) pVfs = sqlite3_vfs_find(0);
|
||||
if( zName && pVfs && pVfs->xDelete ){
|
||||
@@ -1279,8 +1274,7 @@ int sqlite3__wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){
|
||||
** defaulting to "main" if zDbName is 0. Returns 0 if no db with the
|
||||
** given name is open.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
sqlite3_vfs * sqlite3__wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
|
||||
SQLITE_WASM_EXPORT2(sqlite3_vfs *,sqlite3__wasm_db_vfs,(sqlite3 *pDb, const char *zDbName)){
|
||||
sqlite3_vfs * pVfs = 0;
|
||||
sqlite3_file_control(pDb, zDbName ? zDbName : "main",
|
||||
SQLITE_FCNTL_VFS_POINTER, &pVfs);
|
||||
@@ -1302,8 +1296,7 @@ sqlite3_vfs * sqlite3__wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
|
||||
** Returns 0 on success, an SQLITE_xxx code on error. Returns
|
||||
** SQLITE_MISUSE if pDb is NULL.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_db_reset(sqlite3 *pDb){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_reset,(sqlite3 *pDb)){
|
||||
int rc = SQLITE_MISUSE;
|
||||
if( pDb ){
|
||||
sqlite3_table_column_metadata(pDb, "main", 0, 0, 0, 0, 0, 0, 0);
|
||||
@@ -1384,10 +1377,10 @@ int sqlite3__wasm_db_export_chunked( sqlite3* pDb,
|
||||
** If `*pOut` is not NULL, the caller is responsible for passing it to
|
||||
** sqlite3_free() to free it.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
|
||||
unsigned char **pOut,
|
||||
sqlite3_int64 *nOut, unsigned int mFlags ){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_serialize,
|
||||
(sqlite3 *pDb, const char *zSchema,
|
||||
unsigned char **pOut,
|
||||
sqlite3_int64 *nOut, unsigned int mFlags)){
|
||||
unsigned char * z;
|
||||
if( !pDb || !pOut ) return SQLITE_MISUSE;
|
||||
if( nOut ) *nOut = 0;
|
||||
@@ -1448,11 +1441,9 @@ int sqlite3__wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
|
||||
** portability, so that the API can still work in builds where BigInt
|
||||
** support is disabled or unavailable.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_vfs_create_file( sqlite3_vfs *pVfs,
|
||||
const char *zFilename,
|
||||
const unsigned char * pData,
|
||||
int nData ){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_vfs_create_file,
|
||||
(sqlite3_vfs *pVfs, const char *zFilename,
|
||||
const unsigned char * pData, int nData)){
|
||||
int rc;
|
||||
sqlite3_file *pFile = 0;
|
||||
sqlite3_io_methods const *pIo;
|
||||
@@ -1538,10 +1529,9 @@ int sqlite3__wasm_vfs_create_file( sqlite3_vfs *pVfs,
|
||||
** zFilename, appends pData bytes to it, and returns 0 on success or
|
||||
** SQLITE_IOERR on error.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_posix_create_file( const char *zFilename,
|
||||
const unsigned char * pData,
|
||||
int nData ){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_posix_create_file,
|
||||
(const char *zFilename, const unsigned char * pData,
|
||||
int nData)){
|
||||
int rc;
|
||||
FILE * pFile = 0;
|
||||
int fileExisted = 0;
|
||||
@@ -1561,22 +1551,30 @@ int sqlite3__wasm_posix_create_file( const char *zFilename,
|
||||
** This function is NOT part of the sqlite3 public API. It is strictly
|
||||
** for use by the sqlite project's own JS/WASM bindings.
|
||||
**
|
||||
** Allocates sqlite3KvvfsMethods.nKeySize bytes from
|
||||
** sqlite3__wasm_pstack_alloc() and returns 0 if that allocation fails,
|
||||
** else it passes that string to kvstorageMakeKey() and returns a
|
||||
** NUL-terminated pointer to that string. It is up to the caller to
|
||||
** use sqlite3__wasm_pstack_restore() to free the returned pointer.
|
||||
** This returns either a pointer to a static buffer or zKeyIn directly
|
||||
** (if zClass is NULL or empty).
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
char * sqlite3__wasm_kvvfsMakeKeyOnPstack(const char *zClass,
|
||||
const char *zKeyIn){
|
||||
SQLITE_WASM_EXPORT2(const char *,sqlite3__wasm_kvvfsMakeKey,
|
||||
(const char *zClass, const char *zKeyIn)){
|
||||
static char buf[SQLITE_KVOS_SZ+1] = {0};
|
||||
assert(sqlite3KvvfsMethods.nKeySize>24);
|
||||
char *zKeyOut =
|
||||
(char *)sqlite3__wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize);
|
||||
if(zKeyOut){
|
||||
kvstorageMakeKey(zClass, zKeyIn, zKeyOut);
|
||||
if( zClass && *zClass ){
|
||||
kvrecordMakeKey(zClass, zKeyIn, buf);
|
||||
return buf;
|
||||
}else{
|
||||
#if 1
|
||||
/* We can return zKeyIn here only because the JS API takes special
|
||||
** care with its lifetime.*/
|
||||
return zKeyIn;
|
||||
#else
|
||||
/* It would be nice to be able to return zKeyIn directly here, but
|
||||
** it may have been allocated as part of the automated JS-to-WASM
|
||||
** conversions, in which case it will be freed before reaching the
|
||||
** caller. */
|
||||
sqlite3_snprintf(KVRECORD_KEY_SZ, buf, "%s", zKeyIn);
|
||||
return buf;
|
||||
#endif
|
||||
}
|
||||
return zKeyOut;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1586,8 +1584,7 @@ char * sqlite3__wasm_kvvfsMakeKeyOnPstack(const char *zClass,
|
||||
** Returns the pointer to the singleton object which holds the kvvfs
|
||||
** I/O methods and associated state.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
sqlite3_kvvfs_methods * sqlite3__wasm_kvvfs_methods(void){
|
||||
SQLITE_WASM_EXPORT2(sqlite3_kvvfs_methods *,sqlite3__wasm_kvvfs_methods,(void)){
|
||||
return &sqlite3KvvfsMethods;
|
||||
}
|
||||
|
||||
@@ -1602,8 +1599,8 @@ sqlite3_kvvfs_methods * sqlite3__wasm_kvvfs_methods(void){
|
||||
** sqlite3_vtab_config(), or SQLITE_MISUSE if the 2nd arg is not a
|
||||
** valid value.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_vtab_config(sqlite3 *pDb, int op, int arg){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_vtab_config,
|
||||
(sqlite3 *pDb, int op, int arg)){
|
||||
switch(op){
|
||||
case SQLITE_VTAB_DIRECTONLY:
|
||||
case SQLITE_VTAB_INNOCUOUS:
|
||||
@@ -1623,8 +1620,8 @@ int sqlite3__wasm_vtab_config(sqlite3 *pDb, int op, int arg){
|
||||
** Wrapper for the variants of sqlite3_db_config() which take
|
||||
** (int,int*) variadic args.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_ip,
|
||||
(sqlite3 *pDb, int op, int arg1, int* pArg2)){
|
||||
switch(op){
|
||||
case SQLITE_DBCONFIG_ENABLE_FKEY:
|
||||
case SQLITE_DBCONFIG_ENABLE_TRIGGER:
|
||||
@@ -1647,6 +1644,7 @@ int sqlite3__wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
|
||||
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;
|
||||
}
|
||||
@@ -1659,8 +1657,9 @@ int sqlite3__wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
|
||||
** Wrapper for the variants of sqlite3_db_config() which take
|
||||
** (void*,int,int) variadic args.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_pii,
|
||||
(sqlite3 *pDb, int op, void * pArg1, int arg2,
|
||||
int arg3)){
|
||||
switch(op){
|
||||
case SQLITE_DBCONFIG_LOOKASIDE:
|
||||
return sqlite3_db_config(pDb, op, pArg1, arg2, arg3);
|
||||
@@ -1675,8 +1674,8 @@ int sqlite3__wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, in
|
||||
** Wrapper for the variants of sqlite3_db_config() which take
|
||||
** (const char *) variadic args.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_db_config_s,(sqlite3 *pDb, int op,
|
||||
const char *zArg)){
|
||||
switch(op){
|
||||
case SQLITE_DBCONFIG_MAINDBNAME:
|
||||
return sqlite3_db_config(pDb, op, zArg);
|
||||
@@ -1692,8 +1691,7 @@ int sqlite3__wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
|
||||
** Binding for combinations of sqlite3_config() arguments which take
|
||||
** a single integer argument.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_config_i(int op, int arg){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_config_i,(int op, int arg)){
|
||||
return sqlite3_config(op, arg);
|
||||
}
|
||||
|
||||
@@ -1704,8 +1702,7 @@ int sqlite3__wasm_config_i(int op, int arg){
|
||||
** Binding for combinations of sqlite3_config() arguments which take
|
||||
** two int arguments.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_config_ii(int op, int arg1, int arg2){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_config_ii,(int op, int arg1, int arg2)){
|
||||
return sqlite3_config(op, arg1, arg2);
|
||||
}
|
||||
|
||||
@@ -1716,8 +1713,7 @@ int sqlite3__wasm_config_ii(int op, int arg1, int arg2){
|
||||
** Binding for combinations of sqlite3_config() arguments which take
|
||||
** a single i64 argument.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_config_j,(int op, sqlite3_int64 arg)){
|
||||
return sqlite3_config(op, arg);
|
||||
}
|
||||
|
||||
@@ -1729,8 +1725,7 @@ int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){
|
||||
** sqlite3_mprintf()'s %Q modifier (if addQuotes is true) or %q (if
|
||||
** addQuotes is 0). Returns NULL if z is NULL or on OOM.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){
|
||||
SQLITE_WASM_EXPORT2(char *,sqlite3__wasm_qfmt_token,(char *z, int addQuotes)){
|
||||
char * rc = 0;
|
||||
if( z ){
|
||||
rc = addQuotes
|
||||
@@ -1740,6 +1735,21 @@ char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is NOT part of the sqlite3 public API. It is strictly
|
||||
** for use by the sqlite project's own JS/WASM bindings.
|
||||
**
|
||||
** A WASM wrapper for the interal os_kv.c:kvvfsDecode() for internal
|
||||
** use by the kvvfs v2 API.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_kvvfs_decode,(const char *a, char *aOut, int nOut)){
|
||||
return kvvfsDecode(a, aOut, nOut);
|
||||
}
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_kvvfs_encode,(const char *a, int nA, char *aOut)){
|
||||
return kvvfsEncode(a, nA, aOut);
|
||||
}
|
||||
|
||||
|
||||
#if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS)
|
||||
#include <emscripten/console.h>
|
||||
#include <emscripten/wasmfs.h>
|
||||
@@ -1765,8 +1775,7 @@ char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){
|
||||
** the virtual FS fails. In builds compiled without SQLITE_ENABLE_WASMFS
|
||||
** defined, SQLITE_NOTFOUND is returned without side effects.
|
||||
*/
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_init_wasmfs(const char *zMountPoint){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_init_wasmfs,(const char *zMountPoint)){
|
||||
static backend_t pOpfs = 0;
|
||||
if( !zMountPoint || !*zMountPoint ) zMountPoint = "/opfs";
|
||||
if( !pOpfs ){
|
||||
@@ -1785,8 +1794,7 @@ int sqlite3__wasm_init_wasmfs(const char *zMountPoint){
|
||||
return pOpfs ? 0 : SQLITE_NOMEM;
|
||||
}
|
||||
#else
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_init_wasmfs(const char *zUnused){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_init_wasmfs,(const char *zUnused)){
|
||||
//emscripten_console_warn("WASMFS OPFS is not compiled in.");
|
||||
(void)zUnused;
|
||||
return SQLITE_NOTFOUND;
|
||||
@@ -1795,52 +1803,43 @@ int sqlite3__wasm_init_wasmfs(const char *zUnused){
|
||||
|
||||
#if SQLITE_WASM_ENABLE_C_TESTS
|
||||
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_test_intptr(int * p){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_test_intptr,(int * p)){
|
||||
return *p = *p * 2;
|
||||
}
|
||||
|
||||
SQLITE_WASM_EXPORT
|
||||
void * sqlite3__wasm_test_voidptr(void * p){
|
||||
SQLITE_WASM_EXPORT2(void *,sqlite3__wasm_test_voidptr,(void * p)){
|
||||
return p;
|
||||
}
|
||||
|
||||
SQLITE_WASM_EXPORT
|
||||
int64_t sqlite3__wasm_test_int64_max(void){
|
||||
SQLITE_WASM_EXPORT2(int64_t,sqlite3__wasm_test_int64_max,(void)){
|
||||
return (int64_t)0x7fffffffffffffff;
|
||||
}
|
||||
|
||||
SQLITE_WASM_EXPORT
|
||||
int64_t sqlite3__wasm_test_int64_min(void){
|
||||
SQLITE_WASM_EXPORT2(int64_t,sqlite3__wasm_test_int64_min,(void)){
|
||||
return ~sqlite3__wasm_test_int64_max();
|
||||
}
|
||||
|
||||
SQLITE_WASM_EXPORT
|
||||
int64_t sqlite3__wasm_test_int64_times2(int64_t x){
|
||||
SQLITE_WASM_EXPORT2(int64_t,sqlite3__wasm_test_int64_times2,(int64_t x)){
|
||||
return x * 2;
|
||||
}
|
||||
|
||||
SQLITE_WASM_EXPORT
|
||||
void sqlite3__wasm_test_int64_minmax(int64_t * min, int64_t *max){
|
||||
SQLITE_WASM_EXPORT2(void,sqlite3__wasm_test_int64_minmax,(int64_t * min, int64_t *max)){
|
||||
*max = sqlite3__wasm_test_int64_max();
|
||||
*min = sqlite3__wasm_test_int64_min();
|
||||
/*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/
|
||||
}
|
||||
|
||||
SQLITE_WASM_EXPORT
|
||||
int64_t sqlite3__wasm_test_int64ptr(int64_t * p){
|
||||
SQLITE_WASM_EXPORT2(int64_t,sqlite3__wasm_test_int64ptr,(int64_t * p)){
|
||||
/*printf("sqlite3__wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
|
||||
return *p = *p * 2;
|
||||
}
|
||||
|
||||
SQLITE_WASM_EXPORT
|
||||
void sqlite3__wasm_test_stack_overflow(int recurse){
|
||||
SQLITE_WASM_EXPORT2(void,sqlite3__wasm_test_stack_overflow,(int recurse)){
|
||||
if(recurse) sqlite3__wasm_test_stack_overflow(recurse);
|
||||
}
|
||||
|
||||
/* For testing the 'string:dealloc' whwasmutil.xWrap() conversion. */
|
||||
SQLITE_WASM_EXPORT
|
||||
char * sqlite3__wasm_test_str_hello(int fail){
|
||||
SQLITE_WASM_EXPORT2(char *,sqlite3__wasm_test_str_hello,(int fail)){
|
||||
char * s = fail ? 0 : (char *)sqlite3_malloc(6);
|
||||
if(s){
|
||||
memcpy(s, "hello", 5);
|
||||
@@ -1954,8 +1953,8 @@ static int sqlite3__wasm_SQLTester_strnotglob(const char *zGlob, const char *z){
|
||||
return *z==0;
|
||||
}
|
||||
|
||||
SQLITE_WASM_EXPORT
|
||||
int sqlite3__wasm_SQLTester_strglob(const char *zGlob, const char *z){
|
||||
SQLITE_WASM_EXPORT2(int,sqlite3__wasm_SQLTester_strglob,
|
||||
(const char *zGlob, const char *z)){
|
||||
return !sqlite3__wasm_SQLTester_strnotglob(zGlob, z);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,12 @@
|
||||
slightly simpler client-side interface than the slightly-lower-level
|
||||
Worker API does.
|
||||
|
||||
This script necessarily exposes one global symbol, but clients may
|
||||
freely `delete` that symbol after calling it.
|
||||
In non-ESM builds this file necessarily exposes one global symbol,
|
||||
but clients may freely `delete` that symbol after calling it.
|
||||
*/
|
||||
//#if not defined target:es6-module
|
||||
'use strict';
|
||||
//#/if
|
||||
/**
|
||||
Configures an sqlite3 Worker API #1 Worker such that it can be
|
||||
manipulated via a Promise-based interface and returns a factory
|
||||
@@ -109,10 +111,12 @@
|
||||
the callback is called one time for each row of the result set,
|
||||
passed the same worker message format as the worker API emits:
|
||||
|
||||
{type:typeString,
|
||||
{
|
||||
type:typeString,
|
||||
row:VALUE,
|
||||
rowNumber:1-based-#,
|
||||
columnNames: array}
|
||||
columnNames: array
|
||||
}
|
||||
|
||||
Where `typeString` is an internally-synthesized message type string
|
||||
used temporarily for worker message dispatching. It can be ignored
|
||||
@@ -123,10 +127,9 @@
|
||||
callback.
|
||||
|
||||
At the end of the result set, the same event is fired with
|
||||
(row=undefined, rowNumber=null) to indicate that
|
||||
the end of the result set has been reached. Note that the rows
|
||||
arrive via worker-posted messages, with all the implications
|
||||
of that.
|
||||
(row=undefined, rowNumber=null) to indicate that the end of the
|
||||
result set has been reached. The rows arrive via worker-posted
|
||||
messages, with all the implications of that.
|
||||
|
||||
Notable shortcomings:
|
||||
|
||||
@@ -257,7 +260,9 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = {
|
||||
type: 'module'
|
||||
});
|
||||
//#elif target:es6-module
|
||||
return new Worker(new URL("sqlite3-worker1.js", import.meta.url));
|
||||
return new Worker(new URL("sqlite3-worker1.mjs", import.meta.url),{
|
||||
type: 'module'
|
||||
});
|
||||
//#else
|
||||
let theJs = "sqlite3-worker1.js";
|
||||
if(this.currentScript){
|
||||
@@ -273,15 +278,15 @@ 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('worker1 promiser error',...args)
|
||||
onerror: (...args)=>console.error('sqlite3Worker1Promiser():',...args)
|
||||
}/*defaultConfig*/;
|
||||
|
||||
/**
|
||||
@@ -343,7 +348,8 @@ globalThis.sqlite3Worker1Promiser.v2.defaultConfig =
|
||||
incompatibility.
|
||||
*/
|
||||
export default sqlite3Worker1Promiser.v2;
|
||||
//#endif /* target:es6-module */
|
||||
delete globalThis.sqlite3Worker1Promiser;
|
||||
//#/if /* target:es6-module */
|
||||
//#else
|
||||
/* Built with the omit-oo1 flag. */
|
||||
//#endif if not omit-oo1
|
||||
//#/if if not omit-oo1
|
||||
|
||||
@@ -33,9 +33,9 @@
|
||||
directory from which `sqlite3.js` will be loaded.
|
||||
*/
|
||||
//#if target:es6-bundler-friendly
|
||||
import {default as sqlite3InitModule} from './sqlite3-bundler-friendly.mjs';
|
||||
import sqlite3InitModule from './sqlite3-bundler-friendly.mjs';
|
||||
//#elif target:es6-module
|
||||
return new Worker(new URL("sqlite3.js", import.meta.url));
|
||||
import sqlite3InitModule from './sqlite3.mjs';
|
||||
//#else
|
||||
"use strict";
|
||||
{
|
||||
@@ -49,8 +49,8 @@ import {default as sqlite3InitModule} from './sqlite3-bundler-friendly.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
@@ -44,7 +44,8 @@
|
||||
/** abort() if expr is false. If expr is a function, it
|
||||
is called and its result is evaluated.
|
||||
*/
|
||||
assert: function f(expr, msg){
|
||||
assert: function f(expr, ...msg){
|
||||
msg = msg?.join?.(' ');
|
||||
if(!f._){
|
||||
f._ = ('undefined'===typeof abort
|
||||
? (msg)=>{throw new Error(msg)}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
'use strict';
|
||||
(function(){
|
||||
const T = self.SqliteTestUtil;
|
||||
const T = globalThis.SqliteTestUtil;
|
||||
const toss = function(...args){throw new Error(args.join(' '))};
|
||||
const debug = console.debug.bind(console);
|
||||
const eOutput = document.querySelector('#test-output');
|
||||
@@ -40,7 +40,7 @@
|
||||
const error = function(...args){
|
||||
logHtml('error',...args);
|
||||
};
|
||||
|
||||
|
||||
const runTests = function(sqlite3){
|
||||
const capi = sqlite3.capi,
|
||||
oo = sqlite3.oo1,
|
||||
@@ -51,7 +51,7 @@
|
||||
error("This build is not kvvfs-capable.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const dbStorage = 0 ? 'session' : 'local';
|
||||
const theStore = 's'===dbStorage[0] ? sessionStorage : localStorage;
|
||||
const db = new oo.JsStorageDb( dbStorage );
|
||||
@@ -108,7 +108,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
sqlite3InitModule(self.sqlite3TestModule).then((sqlite3)=>{
|
||||
sqlite3InitModule(globalThis.sqlite3TestModule).then((sqlite3)=>{
|
||||
runTests(sqlite3);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
|
||||
<link rel="stylesheet" href="common/emscripten.css"/>
|
||||
<link rel="stylesheet" href="common/testing.css"/>
|
||||
//#if target=es6-module
|
||||
<title>worker-promise (via ESM) tests</title>
|
||||
//#if target:es6-module
|
||||
<title>Worker1-promiser (ESM) tests</title>
|
||||
//#else
|
||||
<title>worker-promise tests</title>
|
||||
//#endif
|
||||
<title>Worker1-promiser tests</title>
|
||||
//#/if
|
||||
</head>
|
||||
<body>
|
||||
<header id='titlebar'><span>worker-promise tests</span></header>
|
||||
@@ -32,11 +32,11 @@
|
||||
<hr>
|
||||
<div id='test-output'></div>
|
||||
<script src="common/SqliteTestUtil.js"></script>
|
||||
//#if target=es6-module
|
||||
//#if target:es6-module
|
||||
<script src="demo-worker1-promiser.mjs" type="module"></script>
|
||||
//#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);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
'use strict';
|
||||
(function(){
|
||||
const T = self.SqliteTestUtil;
|
||||
const T = globalThis.SqliteTestUtil;
|
||||
const SW = new Worker("jswasm/sqlite3-worker1.js");
|
||||
const DbState = {
|
||||
id: undefined
|
||||
@@ -323,7 +323,7 @@
|
||||
switch(ev.result){
|
||||
case 'worker1-ready':
|
||||
log("Message:",ev);
|
||||
self.sqlite3TestModule.setStatus(null);
|
||||
globalThis.sqlite3TestModule.setStatus(null);
|
||||
runTests();
|
||||
return;
|
||||
default:
|
||||
@@ -344,5 +344,5 @@
|
||||
};
|
||||
log("Init complete, but async init bits may still be running.");
|
||||
log("Installing Worker into global scope SW for dev purposes.");
|
||||
self.SW = SW;
|
||||
globalThis.SW = SW;
|
||||
})();
|
||||
|
||||
@@ -175,10 +175,6 @@
|
||||
"features (e.g. upload) do not yet work with OPFS.");
|
||||
}
|
||||
stdout('\nEnter ".help" for usage hints.');
|
||||
this.exec([ // initialization commands...
|
||||
'.nullvalue NULL',
|
||||
'.headers on'
|
||||
].join('\n'));
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
-->
|
||||
<script src="jqterm/jquery.terminal.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="jqterm/jquery.terminal.min.css">
|
||||
//#endif
|
||||
//#/if
|
||||
<style>
|
||||
/* The following styles are for app-level use. */
|
||||
:root {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user