0
0
mirror of https://github.com/tursodatabase/libsql.git synced 2025-05-14 02:52:56 +00:00
Files
libsql/libsql-sqlite3/ext/wasm/tester1.c-pp.js
Pekka Enberg f996bf9f18 Merge upstream SQLite 3.45.1 (#1054)
* Remove unused elements from the json_tree() cursor.

FossilOrigin-Name: 914a50117d477b2cd30d58388fb8d1b71ff7ff6842ba025f38efc6e9647d06d0

* Same results as the legacy JsonNode implementation on a small set of test cases.

FossilOrigin-Name: c3da4b079a1a15a4c0b1a6e71f876648b1d9eb32eddc67b9946c2475c7b6d085

* Fix corner-case error conditions.

FossilOrigin-Name: ec23d34ab75e1d7e9366e59c633e0d30def8759f6d4717583ebeb4c90aeccf0d

* All tests passing.

FossilOrigin-Name: b5a5660ca22437640c9bf32c44d92c76a7293dafcbaf4fa6a4c171128d64871d

* Give the json_valid() function an optional second argument that determines
what is meant by "valid".

FossilOrigin-Name: a4e19ad43dac81e7655ec03ff69bb99d1d02b0c227034c90fb41415fd4793fe3

* Enhance the (SQLITE_DEBUG-only) json_parse() routine so that it shows a 
decoding of JSONB when given a BLOB argument.

FossilOrigin-Name: af267868562e0799ad691dccad05f17afbc34d609eede8c55f57d209290246ef

* In SQLITE_ENABLE_SETLK_TIMEOUT builds, use blocking locks in place of sleep() when opening a read-transaction.

FossilOrigin-Name: a51ef39998e25e86bd0600e71d15011b12e05f4319608018293bdaecb09e8c97

* Have SQLITE_ENABLE_SETLK_TIMEOUT builds block when locking a read-lock slot.

FossilOrigin-Name: f797baf47cf7859cfd8ce248f4f3087af4551a7040af990333426e5a7c269504

* Add untested (#ifdefed-out) code for the MergePatch algorithm against JSONB.
Add (and test) the jsonBlobEdit() routine that is needed by the new MergePatch.

FossilOrigin-Name: 4d353387fc10e1038cfdd86e66007bf728c231a928e588897bbee0fbfe76f225

* More aggressive use of jsonBlobEdit().  Improvements to the MergePatch
implementation sketch.

FossilOrigin-Name: fbca9570fd2e1465739e4d3a8d9bb40fad594fd78ab49b2cb34efa27ebdd8361

* The json_patch() code for JSONB compiles and works sometimes, but there are
still issues.  Incremental check-in.

FossilOrigin-Name: e0099464a0045a04f4ccf29bc2b8325fc8c7f39ccf4847e74818f928c9153588

* All legacy tests are passing.

FossilOrigin-Name: 2c436806b8d5f57de99c00f6154b038454fb9ae427d00d7b4a46ab9c7c69bcb9

* Handle an SQLITE_BUSY_TIMEOUT error if one occurs while attempting a shared lock on a read-lock slot.

FossilOrigin-Name: 5fbf3906d272df3eb981f67455eb35f649ad2774cba9fc3f077b28d9bef3f0cb

* The json_remove() function now uses only JSONB, never JsonNodes, internally.

FossilOrigin-Name: b69786e746ae2b927b64d9871fd120b7f8f06cc53739fd46a4da51aa16cf8576

* Attempt to get json_extract() working with pure JSONB only, and without
the use of JsonNode.  Mostly working, but there are some differences from
legacy in corner cases.

FossilOrigin-Name: 8c324af1eca27e86adc45622af4f3b06a67a3f968596ac58aa7434b1f6f05f3c

* Preserve flexibility in the format of the RHS of -> and ->> operators found
in legacy.

FossilOrigin-Name: 6231ec43adb7436195eb1497de39a6c13c6b4f1c5032e6ea52515d214e61fdbc

* Do not set the J subtype when the output is JSONB.

FossilOrigin-Name: 4f106b64fe8988435872806bd0a6c223b61f53af0dd1c47c847bb4eec4e03e27

* Convert the json_array_length() function to use JSONB instead of JsonNodes.

FossilOrigin-Name: 5ab790736d943e08f097efcee5cfbf0d83c65b0a53f273060330ba719affa5e5

* The assertion change at check-in [7946c79567b0ccd3] is insufficient to fix
the problem of a Table object being deleted out from under the OP_VCheck
opcode.  We need to reference count the Table, which is accomplished here.

FossilOrigin-Name: cad269d5e274443c39203a56603b991accc0399135d436996fc039d1d28ec9db

* In the recovery extension, if a payload size is unreasonably large, it is
probably corrupt, so truncate it.

FossilOrigin-Name: 988c3179e978a3a6d42541e9c7a2ab98150383671810926503376ed808f150ff

* Fix signed integer overflow in fts5.

FossilOrigin-Name: 60e46c7ec68fd8caaed960ca06d98fb06855b2d0bb860dd2fb7b5e89a5e9c7b4

* The json_patch() function now operates exclusively on JSONB.  This patch
also includes improvements to JSONB debug printing routines.

FossilOrigin-Name: fee19d0098242110d2c44ec7b9620c1210ef3f87913305f66ec85d277dd96ab6

* Convert the json_error_position() routine to use only JSONB internally.

FossilOrigin-Name: e7a8ba35bff6fde55827f978de5b343b6c134c7fa53827f5c63915a9dc2598ad

* Convert json_insert(), json_replace(), json_set() to use JSONB internally.
Mostly working, but some corner cases are still not quite right.

FossilOrigin-Name: 99c8f6bd5c9a31b6d00f92e383bec8a8235ed553916ad59adbb1b7663f6ebff1

* Update some OPFS-related help text in WASM tests. Minor cleanups in speedtest1-worker.js.

FossilOrigin-Name: 263f6d3a7784ef7d032dbf7a3265aca8dd70bf50797f28f6b2e8ddb6a301f83a

* New test cases for insert/set/replace with paths that indicate substructure
that does not yet exist.

FossilOrigin-Name: 146c717c51940b2139befc45ac74e7a1c36ef3c32fd3cfe35b334488eebe6298

* New JSON test cases showing insert or set with missing substructure.

FossilOrigin-Name: 6802b6459d0d16c961ff41d240a6c88287f197d8f609090f79308707490a49c2

* Simplification of the new JSON insert/set test cases.

FossilOrigin-Name: 04c0d5644372446c924a2e31a26edf51ddc563a1990d170b0ed4739e3e8b239b

* Enhance json_set() and json_insert() so that they create missing
substructure.

FossilOrigin-Name: cc7a641ab5ae739d31c24f0ad0caeb15a481a63fa8f13720718ea922c25862ff

* Convert json_type() to use JSONB internally.

FossilOrigin-Name: 83074835b900ce85cf67059e674ce959801505c37592671af25ca0af7ed483f1

* Add a basic batch-mode SQL runner for the SAH Pool VFS, for use in comparing it against WebSQL. Bring the WebSQL batch runner up to date, noting that it cannot run without addition of an "origin trial" activation key from Google because that's now the only way to enable WebSQL in Chrome (that part is not checked in because that key is private). Minor code-adjacent cleanups.

FossilOrigin-Name: 883990e7938c1f63906300a6113f0fadce143913b7c384e8aeb5f886f0be7c62

* Convert json_valid() over to using only JSONB as its internal format.

FossilOrigin-Name: 7b5756fa6d00b093bf083a8d7a5ef5485f7a09e4eac473785c8380688f861a1b

* Remove all trace of JsonNode from the JSON implementation.  The JSONB format
is used as the internal binary encoding for searching and editing.

FossilOrigin-Name: 11ebb5f712cc7a515e2e0f2be8c1d71de20c97fe5b74c4f4d72c84fd21182d35

* First attempt to get the JSON text-to-binary cache working.  All test cases
pass, but the cache seems not to help much.

FossilOrigin-Name: 25ed295f300fea6185104a73721076bccd2b2a6e411c78564266fa6dca4ff70c

* Cache is working better, but does not preserve the hasJson5 flag.

FossilOrigin-Name: a12add7ab9f5aee5bb2ede0c4d22e599dd28f7a107dce72b2ea48ef92d233e8a

* Fix up the JSON cache to work better.

FossilOrigin-Name: 1fdbc39521f63aedc6f08ecaafa54ea467b8c6316a692a18ad01eecbf22a0977

* Different approach to querying a tokendata=1 table. Saves cpu and memory.

FossilOrigin-Name: c523f40895866e6fc979a26483dbea8206126b4bbdf4b73b77263c09e13c855e

* Remove old code for tokendata=1 queries.

FossilOrigin-Name: b0a489e8e1bf0290c2117ab32d78b1cc7d67bcb226b55ec044c8367ebde3815b

* Performance optimization in the JSON parser.

FossilOrigin-Name: 68d191f40e708962ec88e0c245b4496bc4a671300484b1cc0f3fc7e6d199a6e6

* Fix harmless compiler warnings and enhance performance the parser.

FossilOrigin-Name: 285633da6d188547e52f07779e209c9e5f3dc33ce0668e14858f3337889ef4b8

* Unroll a loop in the parser for a performance increase.

FossilOrigin-Name: a6dc29e4d5e13949e0fcd9d5dde575c2670eb10a230ab9df3806fc8c3016c540

* Remove a NEVER that can be true if a virtual table column is declared to have
a DEFAULT.  See
[forum:/forumpost/3d4de8917627d058|forum post 3d4de8917627d058].

FossilOrigin-Name: 8abc2ccaf8106f20243568cd7fa74174386eb85d7ea381201e97e2fd527033e0

* Simplification and optimization of the JSON parser.

FossilOrigin-Name: f5ec9485119a2a6cb33eb864c7ca9b41d4a2ed08ab6ad9a6b0dd9358ab253576

* Performance optimization in jsonAppendString().

FossilOrigin-Name: fdf00e96239c73fb67e2acecc5b95f55a1fc51c3deed4512613c0d6070ce5805

* Minor fix to the header comment on jsonXlateTextToBlob().

FossilOrigin-Name: c3677ba410208c07b711f5f526eb5cf039a8eee49f632c7ae04fa55cdfbb9058

* Fix potential unsigned integer underflow in jsonAppendString().

FossilOrigin-Name: d2fba2cbdc3870d34228c1a9446eced884325acc183900d7dd0b96132570fb4a

* Do not allow a JsonParse object to be considered "editable" after an OOM.

FossilOrigin-Name: c6bacf57bd6fe0fee00c9d41163a270b60997c20659949971bbf5c6c62622bfe

* Protect a memcpy() against OOM conditions.

FossilOrigin-Name: 26144d1c25ae0435db568009ba05e485d23d146f2b1f29f3a426c87860316aed

* Ensure that tokendata=1 queries avoid loading large doclists for queries like "common AND uncommon", just as tokendata=0 queries do.

FossilOrigin-Name: 7bda09ab404a110d57449e149a3281fca8dc4cacf7bd9832ea2a1356ad20fe8e

* Take extra care to ensure that JSONB values that are in cache are actually
owned by the JSON subsystem, and that ownership of such values is not handed
back to the bytecode engine.

FossilOrigin-Name: 1304534001e9ef66c6b12752b69d790bfa3427cc803f87cc48ca22ae12df0fdf

* When tokendata=1 queries require multiple segment-cursors, allow those cursors to share a single array of in-memory tombstone pages.

FossilOrigin-Name: e0175d07e4094db5ea4b0378a5ff480dafb6ba9da86a113fa767c4c89c3c866f

* Fix harmless compiler warnings.  Refactor some identifier names for
clearer presentation.

FossilOrigin-Name: 7e3941502789c5afaf19b08112f464abf5e3cba7f92fc9290af2a0f96127ad9a

* Code and comment cleanup.  Everything should work the same.

FossilOrigin-Name: c640754df0d3ffdad994745f0d0e10c8f19f424b87f6a6e6e269491a0350b950

* Fix various compiler warnings and other problems with the new code on this branch.

FossilOrigin-Name: 3a623cfa173b4035c759cb84985d11d8727053beb383648503987d6ab15c0ef0

* Fix harmless compiler warnings reported by MSVC.

FossilOrigin-Name: 419652c0c82980bd043584dcd2976f91dfff7b926b216d597698299850b855c0

* Implement strict JSONB checking in the json_valid() function.

FossilOrigin-Name: 0f26d38880fcbc207abcc94dbc170a7428bab1b4f0b7731aaf5bee0224000994

* Minor code changes for consistency and to simplify testing.

FossilOrigin-Name: df272bd837910ad9e03e222716a1201a601399664365f1dcf73d5932372518ed

* Do not let bad hexadecimal digits in malformed JSONB cause an assertion fault.

FossilOrigin-Name: 8dec1ba1e5076ff596756e00c1e2ada0245f168a503dd1cadadf848331acfac3

* Enable incorrect JSONB to be rendered into text without hitting an
assertion for a bad whitespace escape in a string.

FossilOrigin-Name: 4d6a9a217df6792b41766b774fb0c0553b45f9104c26a0955bf4a30862d7d7bf

* Ensure that OOM conditions in the generation of the "bad JSON path" error
message result in an SQLITE_NOMEM error.

FossilOrigin-Name: aa0e02b5c26a2ef3d6216a0ed8bc01382be43173485f898cb63f2a8c559f2e74

* Avoid problems when the path argument to json_tree() contains embedded U+0000
characters.

FossilOrigin-Name: 9f055091af01a5dddba1a7e9868ad030c8f206237e1569215cb161e53e54aa71

* Remove dead code.  Improved reporting of errors in JSON inputs.

FossilOrigin-Name: 2eaa738e6b5c1b67b3e57c868d9c3a30eea38a0b3b8b02482f06d57a45b10921

* Back off on the use of strlen() for situations where sqlite3_value_bytes()
will work as well, for performance.

FossilOrigin-Name: 79fb54fbb8b9c30f47cdbd437d24a21542716241e822749e5e28c9fbc449bfa8

* Better pre-scan size estimations for objects in the JSON parser resulting
in fewer reallocations and memmove operations.

FossilOrigin-Name: 526b27f90897f5e35dfff7257daf6c4ce4798d649b09b8aecfb02df0449e3c51

* Repair issues and inefficiencies found during testing.

FossilOrigin-Name: ae973cb1515f9d76409c92a2ca2ffd6b71f32b0b490a4886770e7c1b90f12611

* Add tests for using tokendata=1 and contentless_delete=1 together.

FossilOrigin-Name: a2506b8c9718054912270055638204753c4156bbc115e55194e6df9d7e76cb10

* Two new NEVER macros.

FossilOrigin-Name: 52632c92cb06faf0e804654b3490fd6c199521107bd30c8fcbc3a2a5a488098f

* Remove reachable ALWAYS and NEVER macros.

FossilOrigin-Name: f601de3eeabd85993c1f5ee96b62de6fdabbeae2fe8950e00d08feb48d42c498

* Fix bug in xInstToken() causing the wrong token to be returned.

FossilOrigin-Name: da78d07e77cbc783fbc725758911c230fd6a1c1885d9576125de955dcc2bd37f

* Continuing simplifications and code cleanup.

FossilOrigin-Name: ddf92b5059a9106753fd18b82ba8daa269a62af947561c460790107b83416f0b

* Fix a problem with the xInstCount() API and "ORDER BY rank" queries.

FossilOrigin-Name: 317a50563d9e8586fda136e513727241b414e7267d50a06571c8ebd0eae710bc

* Fix memory leak in new code on this branch.

FossilOrigin-Name: ebc160b9a05568df66f86e30804399ee29d34b44a60c57e062f98cb92826353f

* Fixes for xInstToken() with tokendata=0 tables. And with prefix queries.

FossilOrigin-Name: 78fbb71598b1ca756acc078253880a1d0f7983a5a26b9efc683e6488122505a1

* Fix errors in rendering JSON5 escape sequences embedded in JSONB.

FossilOrigin-Name: f1a51ae3863557526a51c6e98e71fcdf4f1ed14a36212b3c90f7408f926345e4

* Do not make the input JSONB editable in json_remove() if there are no PATH
argument.

FossilOrigin-Name: 66594544f3ba9977475a3e3f74404eb2b2fb845053b28bd24c2b52c7df94e9d7

* Fixes to error handling in json_array_length().

FossilOrigin-Name: aa85df2d26b74c171c55bde19ef17c4f11f40b8af7181bbf7162f87cdea7e88b

* Add further tests for xInstToken().

FossilOrigin-Name: 8582707f16133f003a6687f68cbea03d4eb6c2a0e2e07746b7cace0c44e84fa4

* Rename the internal routine jsonMergePatchBlob() to just jsonMergePatch().

FossilOrigin-Name: ebf667b616235bb64b83832008342ba5e7b10b2c170d7cebc431f040fef7ecfb

* Fix OOM and corrupt JSONB handling in json_patch().

FossilOrigin-Name: 1910feb0b7d5cc2b810c3322f6cca281d8730182d30d162bd7bb56800979ea91

* Use an assert() to fix a harmless static analyzer warning.

FossilOrigin-Name: a249ca657e624028bc6b3d2c2bcedd7162d118addb7d62ce519920cecebf1860

* Clean up the JSONB performance test script.

FossilOrigin-Name: 905301075a7fc1010ee7e754867b1b698c9b8576d50e98125def32a5dfb7ee9d

* Small performance gain by unwinding the string literal delimiter search
loop in the JSON parser by one more level.

FossilOrigin-Name: 4c587feac153e8ebe526559ec3d254f545f81e8d1ed3126f91a5ff25ec4aa72e

* Use strspn() to accelerate whitespace bypass in the JSON parser.

FossilOrigin-Name: 843197df08352bdff4b87be91d160e574572aded0d0c66142fd960000c0b4701

* Miscellaneous comment cleanup and typo fixes.

FossilOrigin-Name: 59446dc0bd0091572122a3c8b4653d7a2dc867d16c4a5919f79b81bc3a673ce3

* Further tests for the new code on this branch.

FossilOrigin-Name: 59d008b6c23ab900377bc696ee19381feb7614bac80546eae361e401c3620c4e

* Use extra assert() statement to silence harmless static analyzer warnings.

FossilOrigin-Name: 174c2b2eef5fecd96a5fc89b81032fe81f7801f12097cea10e7e7f0a02114813

* README.md typo fix reported in the forum and update all links from http: to https:.

FossilOrigin-Name: 5c48acdbb44185b352b54911a57a6986d6c7e624bdeba2af48b985d29f0292bf

* Increased rigor in comparisons between object labels in JSON.

FossilOrigin-Name: 2bc86d145fccc07107b7753cb1a69122676d4096fe59c454497bd81a6142d45e

* The rule for the RHS of the ->> and -> operators when the RHS does not begin
with $ is that it must be (1) all digits, or (2) all alphanumerics, or
(3) contained within [..] or else it will become a quoted label.

FossilOrigin-Name: 0e059a546ec11fa5c6d007bd65c249ee2422f1facbdb2792c53e0bc0ccc97e14

* Test cases for object label matching with escape sequences.

FossilOrigin-Name: c6f2aa38e95b7888650cfa7bb773b18a28e01d883033ac77be6d504ffe417d18

* In CLI, move -interactive flag handling back to arg-loop pass 2.

FossilOrigin-Name: 63cb05a862532d2d56e9e81fe32ced09bf58f03146587a118f11c2a84e195e69

* Fix the routine that determines the json_tree.path value for the first row
so that it correctly takes into account escape sequences in the path
argument.

FossilOrigin-Name: b9243ee8a37c62eb8848e765bd4af83bc1b3d3eb24fb4268a1357ad1f8b2e1fb

* Correctly handle 8-byte sizes in the JSONB format.
[forum:/forumpost/283daf08e91183fc|Forum post 283daf08e91183fc].

FossilOrigin-Name: 73d390f39c0bbbc017e01544e4d43c76761f2599bd57f900131c706270dfd202

* Update documentation comments in fts5.h.

FossilOrigin-Name: 38c50e22c98607e6c1fd78d7615cda534773b6d4fd85c712b54749fcd7af0c83

* Work around LLVM's newfound hatred of function pointer casts.
[forum:/forumpost/1a7d257346636292|Forum post 1a7d257346636292].

FossilOrigin-Name: ec0ae4030968c782af48d1c776351c14b2ada21d40aeb97915f33df30706e18f

* Fix compiler warning about shadowed variable in fts5_index.c.

FossilOrigin-Name: ee70e4c1c9c41617850228e48d8df44f105cf2fbbe789340ceca6f27ad6ce5eb

* Improved detection of corrupt JSONB in the jsonReturnFromBlob() function.

FossilOrigin-Name: b014736c1f80ccc46fb4b24ac04310a6ce5cb5b6653665efff366cb3bc742257

* Add ALWAYS() on branches added in [ec0ae4030968c782] that are always true.

FossilOrigin-Name: 451cef8609e96dd9244818adc5c6f240544694bcb4ae620e88f90e403e59d70f

* Rework the jsonEachPathLength() routine in json_tree() so that it is
less susceptible to problems due to goofy object labels.

FossilOrigin-Name: 858b76a00e8ff55215f7a2e6a4cd77fc4d4f98dea7224cd90488744f5ce246a4

* Different fix for the fts5 COMMIT-following-OOM problem first fixed by [fba3129d]. This one does not cause problems if an fts5 table is renamed and then dropped within the same transaction.

FossilOrigin-Name: d8c6b246944934a7a6e027b3f5b986fd64a19dd5c5c5175f4ea8586da59a6764

* Fix a problem with handling OOM and other errors in fts5 when querying tokendata=1 tables.

FossilOrigin-Name: bc911ab5953532956510c199be72b1d3c556f2d0ddbd7fc0ae6f5f917b337b48

* Fix a null-pointer dereference in fts5 tokendata=1 code.

FossilOrigin-Name: d69fa8f0504887f968d9a190ecb889ddb40bb1b56d0d4479f9819c106aec719b

* Avoid an assert() failure when querying an fts5vocab table that accesses a tokendata=1 fts5 table with corrupt %_data records.

FossilOrigin-Name: 386ba9e20423fb2f623d6adc9d3c310fb1b135f54a1dad15ef3b593d97886926

* Ensure an fts5vocab table never uses a special tokendata=1 merge cursor.

FossilOrigin-Name: 1e26510e83b40c9bd2e8bfa2a0e81f2cb915e78fed773204ef537683e48b61dc

* Avoid dropping an error code in new fts5 tokendata=1 code.

FossilOrigin-Name: a66596e33dc9aa4bab2ec3ff45546e1321d0a11bdc764f8381b315292ca92423

* Fix a harmless compiler warning about "confusing indentation".

FossilOrigin-Name: 34f9e9a8c4bea13f60f43062e25cd7d9422f2e7f5b371ed0ddadc9abeb3ca256

* Fix a potential problem RCStr access on a JsonString object that is not
really and RCStr.  Fuzzer/UBSAN find.

FossilOrigin-Name: d2f2174ce2cc89606034e158149a2d05fc3627ec4d5cdb772add7a2250f29d78

* Fix a harmless UBSAN warning.

FossilOrigin-Name: 1503cba6d17e9bade7a5c103ddd23241ff4741f9a2e3032ffe2987af243dae65

* Fix a potential use of uninitialized value in json_valid() with 2nd
argument of 8.

FossilOrigin-Name: fa102036fe46eeb71b7df3e265be1935ae5c78e0b939b08841bcfb8abadbc77a

* Work toward enhanced functionality for json_valid() with deep checking
of the JSONB (second argument has bit 0x08).

FossilOrigin-Name: c370d573198b151767f04e91bf8baa4ae0076751ae468c5709742a0b0ed16770

* Add SQLITE_TESTCTRL_VALIDATE_JSONB, which if enabled under SQLITE_DEBUG causes
cross-checking of generate JSONB.

FossilOrigin-Name: b410a4db74a650003539ffaaea18519d5159b504daac47db6a4874b730f40ac8

* Rename the new test-control to SQLITE_TESTCTRL_JSON_SELFCHECK.  Make it so
that the current value of the setting can be interrogated.

FossilOrigin-Name: 7aff1d9a4cb17ecd5abab21ab032f35a78741dd641ddd8cbcc85fc4a81a0707d

* Activate JSON_SELFCHECK within fuzzcheck.

FossilOrigin-Name: 4d14e733bb521aed65e98533969d2303738232ae87dab70fdf7962e6513195f5

* json_valid(*,8) allows minus-signs on hexadecimal literals.

FossilOrigin-Name: c0d7f4520d839a268b3fd2474d0897a9832aa608bd6238b3e287fabecf07a350

* json_error_position() now uses jsonValidityCheck() to find the approximate
position of an error in a JSONB blob.

FossilOrigin-Name: c3d60cf7028a333b825d5b89516945a73e0c158ac81d8bcc117d21bfd98602c8

* The json_error_position() function now reports an approximate byte offset
to the problem in a JSONB if there is a problem.

FossilOrigin-Name: 80d5d94dff6a2d2557039be3d7d47c1a6003c4b98defe0bd411acfeb963ad5dd

* Validity checking of text nodes in JSONB.

FossilOrigin-Name: fa5160687c2f970d407e8af73c246f7cd806bb4ce35f29a79ac534a8646a6c8e

* Improvements to JSONB validation - catch more cases where the input does
not conform to spec.

FossilOrigin-Name: be1864eac4eb75cc30bf98f73092c8608467f4bd956240df6a0cbea9f1e09e85

* Add NEVER to two unreachable branches in JSON.

FossilOrigin-Name: c96ebb086feb89341565cc52b970ae7799ce1327fe1ad4fc790f1b0dcaa6e229

* Worker1 Promiser API: when multiple db connections are active then use the requested connection instead of always the first-opened connection. Bug reported in [forum:894c330e7f23b177|forum post 894c330e7f23b177].

FossilOrigin-Name: 194276e18e0268829061c09317e7f9f527a703eb45f1755ff1dd30bd99dc1b68

* Fix the JSON object label comparison object so that it works correctly even
if the label ends with escaped whitespace.

FossilOrigin-Name: 4d5353cadd7b7c5f105bc197f3ec739e2d041472d6b3e939654c9f9cfc2749ae

* Improvements to UTF8 handling, and especially the handling of invalid UTF8,
in the JSON routines.

FossilOrigin-Name: 1b229c1101d6c384a30f343c5e47b471ab084b2d8e81170eb8f642afc1c67e3b

* Bug fix in the JSONB validator.
dbsqlfuzz ac6fa521a08609a642198e7decf64180e750b3c4

FossilOrigin-Name: 3e940a6a08b0a0434650cd3d8dd286e09ad8ab805b0a4d515e57bba5d3608577

* Avoid invoking sqlite3ExprColUsage() on an unresolve column reference.
dbsqlfuzz fc34aa62df4de103705d11b807074687ffafbda5.

FossilOrigin-Name: ac9314c0e335694b48c613145f5397247bb88c51806cd0dc3ed4ec306db4bbad

* In CLI, fix .read inability to open 2GB+ files on WIN32.

FossilOrigin-Name: 56c80a62d2e033d64ba5d545ae9cbe3ed7c9d046c0a3fafb6cfa2f0b562d1ef0

* Pass subtype information through the aggregate ORDER BY sorter for
aggregate functions that use subtype information.

FossilOrigin-Name: 3536f4030eab6d650b7ed729d2f71eb6cc3b5fbe16b4e96b99008d66522aaccb

* Improve the error message returned by an fts5 'rebuild' command on an external content table if there is a problem with the content table or view.

FossilOrigin-Name: 0fbf4b8a58fde1c187908934da6f59999b146f32e07ac255cc531c5c4d7007fd

* Fix harmless compiler warnings in JSON and FTS5.

FossilOrigin-Name: 90135efccfeb1046f002bfcbd8dfec9a1a3b40cbe1b5c714ae065b06368e354f

* Add assert()s to FTS5 to fix static analyzer warnings.

FossilOrigin-Name: 27d4a89a5ff96b7b7fc5dc9650e1269f7c7edf91de9b9aafce40be9ecc8b95e9

* Use SQLITE_STRICT_SUBTYPE=1 by default for the JNI and WASM builds unless they're explicitly built with SQLITE_STRICT_SUBTYPE=0.

FossilOrigin-Name: 990211357badf0ab08bd34cf6d25b58849d0fd8503e289c1839fc837a74e1909

* Correct --enable-sab flag in ext/wasm/GNUmakefile to fix a silent alhttpd args-parsing error.

FossilOrigin-Name: 7b9b757d872a31395b0f6454e2309a6a4664b8bdd8749f6a15371cbe72c05b60

* Avoid running the "no_mutex_try" tests with SQLITE_ENABLE_SETLK_TIMEOUT builds as part of the release test.

FossilOrigin-Name: 6b4e1344a28c213cbe8fb97f7f3f6688de93fb73ed96bf460ff74c959da1a712

* Do not run test script fts5origintest4.test with either "memsubsys1" or "mmap" permutations.

FossilOrigin-Name: 05a63d9603ef42cbee6dadff72d97583a9c78e549f70e9a808534d5c1ae7c28a

* Fix a new JSON test case so that it works even if SQLITE_OMIT_VIRTUALTABLE
is defined.

FossilOrigin-Name: b995aae510888a9746b46545d176a0885d4738e1f1bc0b7ad7937ed023efd7d6

* Add mention of --buildonly and --dryrun to the testrunner.tcl usage screen.

FossilOrigin-Name: 23b92d915c12ee768857e2c3c961832f390cad9b53b8bcfc2b97664baab25bb7

* Avoid expiring prepared statements in the middle of an integrity-check.

FossilOrigin-Name: 88beb48472da4667c0727c8ebabe046ea526450ff837fe789d041ed3f1ff105e

* In the count-of-view optimization, deferring freeing obsolete parts of the
parse tree, on the off-chance that some other part of the code might be
holding a pointer to those parts.

FossilOrigin-Name: da442578856c87137eb1677d9b13b7c1cf15828cc41d4756572b278060f69bae

* New test case based on Chromium bug report 1511689.

FossilOrigin-Name: 2c7ef4b4d215f99f8d6787adb64e2037ae96e5dd6cb49c8b81634249f5e1b328

* Enable SQLITE_STRICT_SUBTYPE for default builds of the shell, fuzzcheck,
and testfixture.

FossilOrigin-Name: 5a0c517ed7e46c0f8a3db752cf5b9f8010c60f35084606abe9e7c1c4f993b4a7

* Enhancements to the "randomjson.c" extension.  Automatically load that extension
into fuzzcheck.

FossilOrigin-Name: 70620405ab01d6a5d38bafa9ae175fd6e4eabaf2efb7854734278dafd7b05c99

* Enhancements to ext/misc/randomjson.c.

FossilOrigin-Name: a4e6d1f86f3a502e4170f5a90031e269e48363e95114a66b84d373e3ce0b2704

* Bug fix in the randomjson.c extension.

FossilOrigin-Name: 1f3a33df530dbe330ea8b14a69369b807b413b25a167d1a3938f8f0faf97cc91

* Ensure that all object labels for individual objects generated by
randomjson.c are unique.

FossilOrigin-Name: 29c46aca231b3f1e997ef306a5a651408185bf3ad09ab9fc1fe21ed18caa4d02

* Add randomjson.c to testfixture.  Use it for a new set of invariant tests
against JSON functions.

FossilOrigin-Name: f1c040606bfe784804134d8f3ca130908fad5212b47e3c32792baab977470943

* Ensure that the insert/delete size delta on JSONB objects in the JSON cache
are always set to zero.

FossilOrigin-Name: 4b4581668a908473dbf1322a3e98bc7cca122998c44518ea183af7f0d1ba9f95

* Fix JSON to JSONB translation so that it deals correctly with Infinity
and NaN.

FossilOrigin-Name: 178cb84f36bdb45ba17511900d6d8ea8dfa14912fc5bf7094a20348174a36c95

* Add NEVER() to an unfalsifiable branch.

FossilOrigin-Name: 9a0c67db366d38a0b0741f6a1ae333cf27cfe6f6b7c6eed94bdec9686f9f9f8a

* New JSON invariant test cases.

FossilOrigin-Name: a6a1367b0bf364b1a2e20e153c5f4a578624b8846f9ec0b7c9c3cba0ea2ec346

* Remove a stray comment in the JSON code.

FossilOrigin-Name: 6618bdf0679405b43911ea8cd94050b12a5dc469f3dfe4759ee3ff850a55229e

* Extra ALWAYS() macros to verify state in the sqlite3ExprCanBeNull() routine.

FossilOrigin-Name: be19b84c9f3fe127165809908add148dbe9a827a55608b0490de7e69b7f7f191

* Always make the sqlite_dbdata virtual table available in the CLI.

FossilOrigin-Name: e5fd3b32ad87586a7413570e568c9c1859a37a4f836cca074126471b125fb682

* When unable to resolve an identifier, change the Expr node into TK_NULL
rather than TK_COLUMN, to prevent any downstream misuse of the non-existent
column.  dbsqlfuzz 71869261db80a95e4733afa10ff5724bf3c78592.

FossilOrigin-Name: d2e6117e4f97ab98b01deb5fcad5520f8181d00bed8d904d34963c01d73df857

* Test case for the previous check-in.

FossilOrigin-Name: df5a07e1a5122e08c2fa6076ac08adb2820f997ee11dd88b84863666899dfb57

* Ignore COLLATE operators when determining whether the result of a subexpression
should be shallow-copied or deep-copied.

FossilOrigin-Name: 34ae36a45e814bed7c8340412c7ef3fc849b82357656d0eb5f0f805e59d846d0

* Add ALWAYS() and NEVER() on branches made unreachable by recent changes.

FossilOrigin-Name: c50e6c2ace49d0928b05cbfd877c621e9a0f77dc4e056ccb1dbe5cf118a00d00

* More precise computation of the size of data structures in the query planner.
Response to [forum:/forumpost/7d8685d49d|Forum post 7d8685d49d].

FossilOrigin-Name: 0c8d88e41167ea92341dd1129be01b596a73f46bdcd5b0dd931441a979c013d0

* Fix harmless compiler warning in the randomjson.c extension.

FossilOrigin-Name: debe7060b16669ada7304ffb9bf7616c8fa30bd286d8be871ed17fd6d64a3d4c

* On second thought, we don't really need sqlite_dbdata accessible to the CLI.

FossilOrigin-Name: 36fe6a61ef8fb393281a5e15119d716521219c7b971fbfd63bdea07d27a78ac9

* Remove redundant conditional from sqlite3ExprCanBeNull().

FossilOrigin-Name: 257f96a2d22c605885fa66220c28cf7dc5941c330bccee3f132b9e7b70d89d30

* In JSON - minor code cleanup and refactoring with a small size reduction
and performance increase.

FossilOrigin-Name: 215fabda38daecdbd38b1eca5a6aafbc61b6a36a8303f1d7164d5a1138e63134

* Avoid harmless integer overflow in pager status statistics gathering.
Response to [forum:/forumpost/7f4cdf23f9|forum post 7f4cdf23f9].

FossilOrigin-Name: 206d8c650d937bc700946c40a82a62ea6bc4a80e5f3fb42d0ae2968de25f0644

* Fix SQLITE_ENABLE_SETLK_TIMEOUT assert() statements in os_unix.c to avoid reading past the end of the unixShmNode.aMutex[] array.

FossilOrigin-Name: 029a05cd2928d43d81e4549cce5388c432e2c9e75e3fa0b2fe6e91021b2fb9ac

* Add internal core-developer-only documentation of the JSONB format.

FossilOrigin-Name: 4d30478863b2a60512010de9ec6e3099bfaf75d4afee20acec536713fe94334d

* Add a new comment to debugging output routine sqlite3WhereLoopPrint() to
remind us of what the various fields of the debug output mean.  No changes
to code.

FossilOrigin-Name: da5f34fd4052432b1ae27bb12e56b358cdc5c1282653d60ed0f0fe62f727e4ee

* Fix a usan complaint about signed integer overflow.

FossilOrigin-Name: e65907e0279f4814ec957f0790777d8b94a86926cd27c52442b311b27efc0185

* Update #ifdef checks in pager.c and util.c to account for [0462a2612d1fc1d0] to resolve the build problem reported in [forum:9819032aac|forum post 9819032aac].

FossilOrigin-Name: 0f22d809a1c6c80e381f6bcd931fe4ec36dca0e28d07ab4f4f7f83c813424f60

* Add the -fno-sanitize-recover=undefined to the sanitizer builds used for sdevtest and release testing. To ensure that any test that provokes undefined behaviour fails.

FossilOrigin-Name: 89563311adb0ab7c7a3eadb11c2e27fbca50c56fce8ca616628facbc00d72b88

* Change parameters on a debugging function to include "const".

FossilOrigin-Name: 94c3e1110c6590261bd30ba317fba4dd94023d69b81a94f4b216cce748fe7489

* Add debugging output routines sqlite3ShowWhereLoop(X) and
sqlite3ShowWhereLoopList(X) that can be invoked from a debugger to show
a summary of the content of a single WhereLoop object or a list of WhereLoop
objects.  No change in release builds.

FossilOrigin-Name: 5db30bcc338aac1cf081de2deec7e60749ae012e2b6f95ccf745623adb4a31dc

* Improvements to the query planner to address the inefficiency described
by [forum/forumpost/2568d1f6e6|forum post 2568d1f6e6].

FossilOrigin-Name: 72fcc12cda910a0e3f7875eb3d117b2a5608705c97703985427a02960f1ab5c5

* Avoid signed integer overflow during integrity_check of FTS5.

FossilOrigin-Name: 5937df3b25799eceaadfb04d7226c9995d44c8d8edb5ac3ad02af9d7e3570726

* Fix harmless compiler warnings associated with [5db30bcc338aac1c]

FossilOrigin-Name: e55d1c2333f35fc20615aa83a7843d08cae7945710a2156d44eee0cc37d90ade

* Remove an ALWAYS() added in [c50e6c2ace49d092] because it is sometimes false.
dbsqlfuzz c393a4f783d42efd9552772110aff7e5d937f15e.

FossilOrigin-Name: b9daf37e57cde12c4de271a2b1995e8e91b6411f8c2e8882e536241929609b3a

* Improved handling of malformed unicode within JSON strings.

FossilOrigin-Name: e252bdf5f5de26ba8e2bcc6b0ad94121ed6fc4d86c02fe4a2a058ada93747beb

* Ensure that the xColumnText(), xQueryPhrase() and xPhraseFirstColumn() APIs all return SQLITE_RANGE if they are passed a bad column or phrase number.

FossilOrigin-Name: 1a8a9b1c89519d265869251e8b6d3c5db733f0d3a7dea6c7962811a8f1157dff

* Fix a problem in the shell tool (not library) causing an out-of-bounds write if an ".open" command failed, then the user pressed ctrl-c to interrupt a query running on the substitute in-memory database.

FossilOrigin-Name: 026618b9e321576f616a32e41329066ba629814170c6cfeef35430343f5003f3

* Enhance the (undocumented, debug-only) json_parse() SQL function so that it
returns the text rendering of the JSONB parse of the input, rather than printing
the rendering on stdout.

FossilOrigin-Name: 056de8d551dcbdf1d162e2db15ed418fa9c786f900cd3972ef8a1dea3f4f3aa1

* Fix harmless compiler warnings in FTS5.

FossilOrigin-Name: 3cd5ef44e40570c357f913a9483fa1cd72e7f2827a5ed5826bff99febae213b1

* Performance improvement by unwinding a loop in jsonAppendString().

FossilOrigin-Name: 190ab3c08431a0ba24d76392eab251f5c1792add05e4ec780998b299208eca95

* Update fts5origintext4.test to work with SQLITE_DIRECT_OVERFLOW_READ.

FossilOrigin-Name: 15ed002aed12556aeb9bbe537c4ba839f0c95bac65a69d03401b37cc3fd11b92

* Enable SQLITE_DIRECT_OVERFLOW_READ unless it is specifically disabled using
the -DSQLITE_DIRECT_OVERFLOW_READ=0 compile-time option.

FossilOrigin-Name: 630604a4e604bfb36c31602917bfa8d42c10c82966d0819932bf8f827b9158b8

* Minor doc touchup in the JS bits.

FossilOrigin-Name: 8d2120c35425081e2158d6a8a6b083c4adf8d694046b2d98f5fd235520920432

* Use SQLITE_ENABLE_STAT4 in both the WASM and JNI builds.

FossilOrigin-Name: 99d11e6d0ae687ff6bac5119027f7b04d5e7185214e79cf8c56289cfa809b0f9

* WASM: various build cleanups and add initial infrastructure for a build which elides the oo1 API and its dependents (worker1 and promiser). Sidebar: an attempt was made to move generation of the build rules to an external script, but the mixed-mode make/script was even less legible than the $(eval) indirection going on in the makefile.

FossilOrigin-Name: 563d313163c02b398ae85b7c2ed231019a14e006726f09a7c1f294a58bf4363f

* JNI: move the ByteBuffer-using APIs from public to package visibility for the time being because they have UB-inducing possibilities which need to be worked out. Update test code to account for a change in custom FTS5 columntext() impls.

FossilOrigin-Name: dc501275fcfab3ad9b6ebbadf7588b225a9dd07a0abac5be83d96f15bfba99e9

* Extra steps taken to avoid using low-quality indexes in a query plan.
This branch accomplishes the same end as the nearby enhanced-stat1 branch,
but with much less change and hence less risk.

FossilOrigin-Name: c030e646262fee43a59b45fdc1630d972f8bf88ac3c142b6bdaf4cbb36695a4f

* Remove some unnecessary computations from ANALYZE so that ANALYZE runs with
fewer CPU cycles.  These changes were spotted while working on the nearby
enhanced-stat1 branch.  So even if enhanced-stat1 is abandoned, that effort
put into it will not have been in vain.

FossilOrigin-Name: 5527e8c4abb904b1a438ec1c353d4a960bf82faaf3a2c742af1df7c613850441

* Back out [99d11e6d0ae6] (enabling of STAT4 in WASM/JNI), per /chat discussion.

FossilOrigin-Name: cd7929ee2e2c305475fa5a4dff2edaccf90067126ef04a1c2714cf464925453f

* Update and clean up the in-makefile docs for ext/wasm.

FossilOrigin-Name: 7a7b295e6d7e95ee4a46cc42761895d11700ab295870c5a4380072bb4a5b7099

* Elaborate on the various build flavors used by ext/wasm/. Doc changes only.

FossilOrigin-Name: d489232aa492618d4c8e5817addb2323d0ca067742d7140216914239a66fb221

* Increase the default "max_page_count" to its theoretical maximum of
4294967294.

FossilOrigin-Name: ffb35f1784a4305b979a850485f57f56938104a3a03f4a7aececde92864c4879

* Fix a problem in fts5 caused by a COMMIT involving fts5 data that immediately follows a ROLLBACK TO that does not.

FossilOrigin-Name: 55c61f6a8d6a1bc79497b05669beac5c5397b06382bf24b6bec54845962d219b

* Adjust the sqlite3PagerDirectReadOk() routine (part of the
SQLITE_DIRECT_OVERFLOW_READ optimization) to use less code and to be
more easily testable.

FossilOrigin-Name: eed670ea2a9424f7df4eeb01c152fc38f7190a5e39aa891651b28dc91fcdc019

* Back out [b517a52fa36df0a0] which is no longer reachable due to early
error detection enhancements in [166e82dd20efbfd3].

FossilOrigin-Name: 704943e96f2620b99260667ac9922c2f72bc3e92e2dfe1d9c2a91c7b704564d9

* Update the sqldiff.exe utility program so that it uses the sqlite3_str
string interface, and so that it does console output using the
ext/consio extension.

FossilOrigin-Name: 4443b7e592da97d1cb1b3b79ed0559452d8057a33aba4d184c2fffbf200e05f5

* Enhance sqlite3_analyzer.exe so that it uses the ext/consio extension.

FossilOrigin-Name: 769de0b98e136e4a0945b80216d0c9583c1ccd9de69cb0494875c2300e172646

* Change a constant from decimal to hex to avoid a compiler warning on Mac.

FossilOrigin-Name: e3acb8a43ad544fd5b5341058276bd3b61b6bdb6b719790476a90e0de4320f90

* Convert the JSON functions to use lookaside memory allocation whenever
feasible, to avoid hitting the global memory allocator mutex.

FossilOrigin-Name: a79a244954f728596da3c0e28fa3b887258d1bd831f53881970f418f3fba84c7

* Fix a #ifdef in sqlite3_test_control() that was preventing builds with
SQLITE_OMIT_WSD.

FossilOrigin-Name: d546a9c94caf7408cc6e4530ec190d3a13fae09dc15b71b03d6369e02ee62abd

* Restructure some code to fix what appears to be a false-positive UBSAN warning.

FossilOrigin-Name: fe952c12903ea2150880c8bb57cda2efc00ce9fa801568a68c619e0745f30567

* Avoid errors with SQLITE_OMIT_VIRTUALTABLE builds in json106.test and unionall.test.

FossilOrigin-Name: 90e8a233549a2d31e6959ce3fec927693b772ab3c0abce65e81d7350d2ca5cc6

* Update extension ext/misc/totext.c to avoid both ubsan warnings and dubious real->integer conversions.

FossilOrigin-Name: c626aa108a7a30cef54af8d93ac9e45749568ed38e4e06623a6bad6b4bf6e8ec

* Update JSON performance testing procedures for clarity and to describe how to
do performance testing of JSONB.

FossilOrigin-Name: b115b4f75bc7c4e6d9bab5edf13297f27a36f30083c80d2c502b01208da5dfc0

* Ensure that SQLITE_PROTOCOL is not returned too early when a SQLITE_ENABLE_SETLK_TIMEOUT build fails to open a transaction on a wal mode database in cases where blocking locks are not being used.

FossilOrigin-Name: b934a33671d8a0190082ad7e5e68c78fe0c558d102404eafc1de26e4e7d65b92

* Updates to RTREE to facility testing.

FossilOrigin-Name: 7a5b42ff74882c58493dc8b710fde73d4ff251f5d42271d84be73ceaabc01698

* Remove an ALWAYS() from RTREE.  Dbsqlfuzz found a way to make it false.

FossilOrigin-Name: 40f0a29e6dd90fcb969d7c0e49728ba0ee8f31d9e8f502b9a21469620a8ad283

* Minor change to os_unix.c to facilitate 100% MC/DC testing.

FossilOrigin-Name: 0dfa7b4da134db281c3c4eddb4569c53a450f955f0af2f410e13db801aff4ea2

* Automatically turn off DEFENSIVE mode in the shell tool when executing scripts generated by the ".dump" command against an empty database. Add a warning to the top of generated ".dump" scripts that populate virtual tables.

FossilOrigin-Name: 6e9e96b7e7afb9420110f4b93d10b945c9eadfde5e9c81e59ae9ee8167e75707

* Fix date on new file shell9.test.

FossilOrigin-Name: c82da712113d5dcd63b764dbc68842026989627abc840acb4a33f3a4972b832a

* Improved resolution of unqualified names in the REINDEX command.
[forum:/info/74cd0ceabd|Forum thread 74cd0ceabd].

FossilOrigin-Name: 97709ce2a1f5ae05495e412ca27108048e5b8a63a1e3bca4be13933f7527da7b

* Put an SQLITE_ENABLE_SETLK_TIMEOUT branch inside the appropriate ifdef with
an assert on the else since the condition is always false if SETLK_TIMEOUT
is not available.

FossilOrigin-Name: d81e7a036ac5d70b6a6ee6ab7d81e041c1f5fc04b70bcee47e203d521caf7e93

* In fts5, flush the contents of the in-memory hash table whenever the secure-delete option is toggled. This prevents spurious corruption reports under some circumstances.

FossilOrigin-Name: ccf552319a62bfb329820a3bc1f490bacbaa6e90694a257fc65a568a605542c3

* Fix a comment in sessions.  No functional changes.
[forum:/forumpost/8c20dc935b|Forum post 8c20dc935b].

FossilOrigin-Name: b0eb6d3628c1f70399a22d9fd3b79a796bc343adfeba50515440db609565961a

* Have the shell tool automatically enable SQLITE_CONFIG_DQS_DDL when executing a ".dump" script against an empty db.

FossilOrigin-Name: f47a5f4e0ce078e6cc1183e6cbb3c4013af379b496efae94863a42e5c39928ed

* Version 3.45.0

FossilOrigin-Name: 1066602b2b1976fe58b5150777cced894af17c803e068f5918390d6915b46e1d

* wasm build: reformulate an awk invocation to account for awks which do not support the -e flag. Problem reported on the forum via a docker-hosted build.

FossilOrigin-Name: 90dd51153fd0a6197e2ee49b5492ad120f0bfc324b60651f3d4f47c286887b46

* When backing out a character in a constructed string in JSON, first make sure
the string has not been reset by on OOM.

FossilOrigin-Name: 950bf9fe7829864e0abe6d71ca0495f346feb5d7943d76c95e55a6b86ea855da

* Ensure that the xIntegrity methods of fts3 and fts5 work on read-only databases.

FossilOrigin-Name: e79b97369fa740f62f695057d4a2cf8dae48a683982ec879f04a19039c9cb418

* When a JSON input is a blob, but it looks like valid JSON when cast to text,
then accept it as valid JSON.  This replicates a long-standing bug in the
behavior of JSON routines, and thus avoids breaking legacy apps.

FossilOrigin-Name: 4c2c1b97dce46a279846380c937ac6de5c367927c6843516641eead7ea6db472

* Bump the version number to 3.45.1

FossilOrigin-Name: 54d34edb89430b266221b7e6eea0afbd2c9dafbe774344469473abc8ad1e13fd

* Fix harmless "unused parameter" compiler warning in the new fts3IntegrityMethod
implementation.

FossilOrigin-Name: 9d459f6b50fb6f995e6284a0815c5e211cacac44aad0b96bf01ba68af97f51fc

* In os_unix.c and os_win.c, do not allow xFetch() to return a pointer to a page buffer that is right at the end of the mapped region - if the database is corrupted in a specific way such a page buffer might be overread by several bytes.

FossilOrigin-Name: d131cab652ac11795322af13d0b330e7e44ab91587a1a3e73fe7b9a14b2dd531

* Slight adjustment to test results for Windows in mmap1.test due to
the previous check-in.

FossilOrigin-Name: a8043eaed899285b5cf4aab0c23c3dabb8975910c353cb579fd1f1655db390f6

* Apply the same fix found in [99057383acc8f920] to descending scans.

FossilOrigin-Name: 593d6a1c2e9256d797f160e867278414e882a3d04d7fea269bea86965eaa7576

* Automatically disable the DISTINCT optimization during query planning if the
ORDER BY clause exceeds 63 terms.

FossilOrigin-Name: 6edbdcc02d18727f68f0236e15dde4ecfc77e6f452b522eb4e1e895929b1fb63

* When rendering JSONB back into text JSON, report an error if a zero-length
integer or floating-point node is encountered.  Otherwise, if the node occurs
at the very end of the JSONB, the rendering logic might read one byte past
the end of the initialized part of the BLOB byte array.  OSSFuzz 66284.

FossilOrigin-Name: 3ab08ac75d97ffd9920f5c924362a4819560b40faa8a4f9100068057f5fa420a

* Avoid a potential buffer overread when handling corrupt json blobs.

FossilOrigin-Name: ac402cc551b2cbe3f8fbbc9c711a04942eab5eeb9d2f4a394e9370d2380427b5

* Detect malformed nested JSONB earlier and stop rendering to avoid long
delays.

FossilOrigin-Name: ab40e282465c989bf249453d7c6f60072a38b691f579411cdf9aad234b20f0f7

* Version 3.45.1

FossilOrigin-Name: e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a

---------

Co-authored-by: drh <>
Co-authored-by: dan <Dan Kennedy>
Co-authored-by: stephan <stephan@noemail.net>
Co-authored-by: larrybr <larrybr@noemail.net>
2024-07-25 08:55:22 +00:00

3238 lines
124 KiB
JavaScript

/*
2022-10-12
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.
***********************************************************************
Main functional and regression tests for the sqlite3 WASM API.
This mini-framework works like so:
This script adds a series of test groups, each of which contains an
arbitrary number of tests, into a queue. After loading of the
sqlite3 WASM/JS module is complete, that queue is processed. If any
given test fails, the whole thing fails. This script is built such
that it can run from the main UI thread or worker thread. Test
groups and individual tests can be assigned a predicate function
which determines whether to run them or not, and this is
specifically intended to be used to toggle certain tests on or off
for the main/worker threads or the availability (or not) of
optional features such as int64 support.
Each test group defines a single state object which gets applied as
the test functions' `this` for all tests in that group. Test
functions can use that to, e.g., set up a db in an early test and
close it in a later test. Each test gets passed the sqlite3
namespace object as its only argument.
*/
/*
This file is intended to be processed by c-pp to inject (or not)
code specific to ES6 modules which is illegal in non-module code.
Non-ES6 module build and ES6 module for the main-thread:
./c-pp -f tester1.c-pp.js -o tester1.js
ES6 worker module build:
./c-pp -f tester1.c-pp.js -o tester1-esm.js -Dtarget=es6-module
*/
//#if target=es6-module
import {default as sqlite3InitModule} from './jswasm/sqlite3.mjs';
globalThis.sqlite3InitModule = sqlite3InitModule;
//#else
'use strict';
//#endif
(function(self){
/**
Set up our output channel differently depending
on whether we are running in a worker thread or
the main (UI) thread.
*/
let logClass;
/* Predicate for tests/groups. */
const isUIThread = ()=>(globalThis.window===self && globalThis.document);
/* Predicate for tests/groups. */
const isWorker = ()=>!isUIThread();
/* Predicate for tests/groups. */
const testIsTodo = ()=>false;
const haveWasmCTests = ()=>{
return !!wasm.exports.sqlite3_wasm_test_intptr;
};
const hasOpfs = ()=>{
return globalThis.FileSystemHandle
&& globalThis.FileSystemDirectoryHandle
&& globalThis.FileSystemFileHandle
&& globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle
&& navigator?.storage?.getDirectory;
};
{
const mapToString = (v)=>{
switch(typeof v){
case 'number': case 'string': case 'boolean':
case 'undefined': case 'bigint':
return ''+v;
default: break;
}
if(null===v) return 'null';
if(v instanceof Error){
v = {
message: v.message,
stack: v.stack,
errorClass: v.name
};
}
return JSON.stringify(v,undefined,2);
};
const normalizeArgs = (args)=>args.map(mapToString);
if( isUIThread() ){
console.log("Running in the UI thread.");
const logTarget = document.querySelector('#test-output');
logClass = function(cssClass,...args){
const ln = document.createElement('div');
if(cssClass){
for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){
ln.classList.add(c);
}
}
ln.append(document.createTextNode(normalizeArgs(args).join(' ')));
logTarget.append(ln);
};
const cbReverse = document.querySelector('#cb-log-reverse');
//cbReverse.setAttribute('checked','checked');
const cbReverseKey = 'tester1:cb-log-reverse';
const cbReverseIt = ()=>{
logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse');
//localStorage.setItem(cbReverseKey, cbReverse.checked ? 1 : 0);
};
cbReverse.addEventListener('change', cbReverseIt, true);
/*if(localStorage.getItem(cbReverseKey)){
cbReverse.checked = !!(+localStorage.getItem(cbReverseKey));
}*/
cbReverseIt();
}else{ /* Worker thread */
console.log("Running in a Worker thread.");
logClass = function(cssClass,...args){
postMessage({
type:'log',
payload:{cssClass, args: normalizeArgs(args)}
});
};
}
}
const reportFinalTestStatus = function(pass){
if(isUIThread()){
let e = document.querySelector('#color-target');
e.classList.add(pass ? 'tests-pass' : 'tests-fail');
e = document.querySelector('title');
e.innerText = (pass ? 'PASS' : 'FAIL') + ': ' + e.innerText;
}else{
postMessage({type:'test-result', payload:{pass}});
}
};
const log = (...args)=>{
//console.log(...args);
logClass('',...args);
}
const warn = (...args)=>{
console.warn(...args);
logClass('warning',...args);
}
const error = (...args)=>{
console.error(...args);
logClass('error',...args);
};
const toss = (...args)=>{
error(...args);
throw new Error(args.join(' '));
};
const tossQuietly = (...args)=>{
throw new Error(args.join(' '));
};
const roundMs = (ms)=>Math.round(ms*100)/100;
/**
Helpers for writing sqlite3-specific tests.
*/
const TestUtil = {
/** Running total of the number of tests run via
this API. */
counter: 0,
/**
If expr is a function, it is called and its result
is returned, coerced to a bool, else expr, coerced to
a bool, is returned.
*/
toBool: function(expr){
return (expr instanceof Function) ? !!expr() : !!expr;
},
/** Throws if expr is false. If expr is a function, it is called
and its result is evaluated. If passed multiple arguments,
those after the first are a message string which get applied
as an exception message if the assertion fails. The message
arguments are concatenated together with a space between each.
*/
assert: function f(expr, ...msg){
++this.counter;
if(!this.toBool(expr)){
throw new Error(msg.length ? msg.join(' ') : "Assertion failed.");
}
return this;
},
/** Calls f() and squelches any exception it throws. If it
does not throw, this function throws. */
mustThrow: function(f, msg){
++this.counter;
let err;
try{ f(); } catch(e){err=e;}
if(!err) throw new Error(msg || "Expected exception.");
return this;
},
/**
Works like mustThrow() but expects filter to be a regex,
function, or string to match/filter the resulting exception
against. If f() does not throw, this test fails and an Error is
thrown. If filter is a regex, the test passes if
filter.test(error.message) passes. If it's a function, the test
passes if filter(error) returns truthy. If it's a string, the
test passes if the filter matches the exception message
precisely. In all other cases the test fails, throwing an
Error.
If it throws, msg is used as the error report unless it's falsy,
in which case a default is used.
*/
mustThrowMatching: function(f, filter, msg){
++this.counter;
let err;
try{ f(); } catch(e){err=e;}
if(!err) throw new Error(msg || "Expected exception.");
let pass = false;
if(filter instanceof RegExp) pass = filter.test(err.message);
else if(filter instanceof Function) pass = filter(err);
else if('string' === typeof filter) pass = (err.message === filter);
if(!pass){
throw new Error(msg || ("Filter rejected this exception: "+err.message));
}
return this;
},
/** Throws if expr is truthy or expr is a function and expr()
returns truthy. */
throwIf: function(expr, msg){
++this.counter;
if(this.toBool(expr)) throw new Error(msg || "throwIf() failed");
return this;
},
/** Throws if expr is falsy or expr is a function and expr()
returns falsy. */
throwUnless: function(expr, msg){
++this.counter;
if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed");
return this;
},
eqApprox: (v1,v2,factor=0.05)=>(v1>=(v2-factor) && v1<=(v2+factor)),
TestGroup: (function(){
let groupCounter = 0;
const TestGroup = function(name, predicate){
this.number = ++groupCounter;
this.name = name;
this.predicate = predicate;
this.tests = [];
};
TestGroup.prototype = {
addTest: function(testObj){
this.tests.push(testObj);
return this;
},
run: async function(sqlite3){
logClass('group-start',"Group #"+this.number+':',this.name);
if(this.predicate){
const p = this.predicate(sqlite3);
if(!p || 'string'===typeof p){
logClass(['warning','skipping-group'],
"SKIPPING group:", p ? p : "predicate says to" );
return;
}
}
const assertCount = TestUtil.counter;
const groupState = Object.create(null);
const skipped = [];
let runtime = 0, i = 0;
for(const t of this.tests){
++i;
const n = this.number+"."+i;
logClass('one-test-line', n+":", t.name);
if(t.predicate){
const p = t.predicate(sqlite3);
if(!p || 'string'===typeof p){
logClass(['warning','skipping-test'],
"SKIPPING:", p ? p : "predicate says to" );
skipped.push( n+': '+t.name );
continue;
}
}
const tc = TestUtil.counter, now = performance.now();
let rc = t.test.call(groupState, sqlite3);
/*if(rc instanceof Promise){
rc = rc.catch((e)=>{
error("Test failure:",e);
throw e;
});
}*/
await rc;
const then = performance.now();
runtime += then - now;
logClass(['faded','one-test-summary'],
TestUtil.counter - tc, 'assertion(s) in',
roundMs(then-now),'ms');
}
logClass(['green','group-end'],
"#"+this.number+":",
(TestUtil.counter - assertCount),
"assertion(s) in",roundMs(runtime),"ms");
if(0 && skipped.length){
logClass('warning',"SKIPPED test(s) in group",this.number+":",skipped);
}
}
};
return TestGroup;
})()/*TestGroup*/,
testGroups: [],
currentTestGroup: undefined,
addGroup: function(name, predicate){
this.testGroups.push( this.currentTestGroup =
new this.TestGroup(name, predicate) );
return this;
},
addTest: function(name, callback){
let predicate;
if(1===arguments.length){
this.currentTestGroup.addTest(arguments[0]);
}else{
this.currentTestGroup.addTest({
name, predicate, test: callback
});
}
return this;
},
runTests: async function(sqlite3){
return new Promise(async function(pok,pnok){
try {
let runtime = 0;
for(let g of this.testGroups){
const now = performance.now();
await g.run(sqlite3);
runtime += performance.now() - now;
}
logClass(['strong','green','full-test-summary'],
"Done running tests.",TestUtil.counter,"assertions in",
roundMs(runtime),'ms');
pok();
reportFinalTestStatus(true);
}catch(e){
error(e);
pnok(e);
reportFinalTestStatus(false);
}
}.bind(this));
}
}/*TestUtil*/;
const T = TestUtil;
T.g = T.addGroup;
T.t = T.addTest;
let capi, wasm/*assigned after module init*/;
const sahPoolConfig = {
name: 'opfs-sahpool-tester1',
clearOnInit: true,
initialCapacity: 6
};
////////////////////////////////////////////////////////////////////////
// End of infrastructure setup. Now define the tests...
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
T.g('Basic sanity checks')
.t({
name:'sqlite3_config()',
test:function(sqlite3){
for(const k of [
'SQLITE_CONFIG_GETMALLOC', 'SQLITE_CONFIG_URI'
]){
T.assert(capi[k] > 0);
}
T.assert(capi.SQLITE_MISUSE===capi.sqlite3_config(
capi.SQLITE_CONFIG_URI, 1
), "MISUSE because the library has already been initialized.");
T.assert(capi.SQLITE_MISUSE === capi.sqlite3_config(
// not enough args
capi.SQLITE_CONFIG_GETMALLOC
));
T.assert(capi.SQLITE_NOTFOUND === capi.sqlite3_config(
// unhandled-in-JS config option
capi.SQLITE_CONFIG_GETMALLOC, 1
));
if(0){
log("We cannot _fully_ test sqlite3_config() after the library",
"has been initialized (which it necessarily has been to",
"set up various bindings) and we cannot shut it down ",
"without losing the VFS registrations.");
T.assert(0 === capi.sqlite3_config(
capi.SQLITE_CONFIG_URI, 1
));
}
}
})/*sqlite3_config()*/
////////////////////////////////////////////////////////////////////
.t({
name: "JS wasm-side allocator",
test: function(sqlite3){
if(sqlite3.config.useStdAlloc){
warn("Using system allocator. This violates the docs and",
"may cause grief with certain APIs",
"(e.g. sqlite3_deserialize()).");
T.assert(wasm.alloc.impl === wasm.exports.malloc)
.assert(wasm.dealloc === wasm.exports.free)
.assert(wasm.realloc.impl === wasm.exports.realloc);
}else{
T.assert(wasm.alloc.impl === wasm.exports.sqlite3_malloc)
.assert(wasm.dealloc === wasm.exports.sqlite3_free)
.assert(wasm.realloc.impl === wasm.exports.sqlite3_realloc);
}
}
})
.t('Namespace object checks', function(sqlite3){
const wasmCtypes = wasm.ctype;
T.assert(wasmCtypes.structs[0].name==='sqlite3_vfs').
assert(wasmCtypes.structs[0].members.szOsFile.sizeof>=4).
assert(wasmCtypes.structs[1/*sqlite3_io_methods*/
].members.xFileSize.offset>0);
[ /* Spot-check a handful of constants to make sure they got installed... */
'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8',
'SQLITE_STATIC', 'SQLITE_DIRECTONLY',
'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE'
].forEach((k)=>T.assert('number' === typeof capi[k]));
[/* Spot-check a few of the WASM API methods. */
'alloc', 'dealloc', 'installFunction'
].forEach((k)=>T.assert(wasm[k] instanceof Function));
T.assert(capi.sqlite3_errstr(capi.SQLITE_IOERR_ACCESS).indexOf("I/O")>=0).
assert(capi.sqlite3_errstr(capi.SQLITE_CORRUPT).indexOf('malformed')>0).
assert(capi.sqlite3_errstr(capi.SQLITE_OK) === 'not an error');
try {
throw new sqlite3.WasmAllocError;
}catch(e){
T.assert(e instanceof Error)
.assert(e instanceof sqlite3.WasmAllocError)
.assert("Allocation failed." === e.message);
}
try {
throw new sqlite3.WasmAllocError("test",{
cause: 3
});
}catch(e){
T.assert(3 === e.cause)
.assert("test" === e.message);
}
try {throw new sqlite3.WasmAllocError("test","ing",".")}
catch(e){T.assert("test ing ." === e.message)}
try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) }
catch(e){
T.assert('SQLITE_SCHEMA' === e.message)
.assert(capi.SQLITE_SCHEMA === e.resultCode);
}
try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) }
catch(e){
T.assert('SQLITE_CORRUPT' === e.message)
.assert(capi.SQLITE_CORRUPT === e.resultCode)
.assert(true===e.cause);
}
try{ sqlite3.SQLite3Error.toss("resultCode check") }
catch(e){
T.assert(capi.SQLITE_ERROR === e.resultCode)
.assert('resultCode check' === e.message);
}
})
////////////////////////////////////////////////////////////////////
.t('strglob/strlike', function(sqlite3){
T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
})
////////////////////////////////////////////////////////////////////
;/*end of basic sanity checks*/
////////////////////////////////////////////////////////////////////
T.g('C/WASM Utilities')
.t('sqlite3.wasm namespace', function(sqlite3){
// TODO: break this into smaller individual test functions.
const w = wasm;
const chr = (x)=>x.charCodeAt(0);
//log("heap getters...");
{
const li = [8, 16, 32];
if(w.bigIntEnabled) li.push(64);
for(const n of li){
const bpe = n/8;
const s = w.heapForSize(n,false);
T.assert(bpe===s.BYTES_PER_ELEMENT).
assert(w.heapForSize(s.constructor) === s);
const u = w.heapForSize(n,true);
T.assert(bpe===u.BYTES_PER_ELEMENT).
assert(s!==u).
assert(w.heapForSize(u.constructor) === u);
}
}
// alloc(), realloc(), allocFromTypedArray()
{
let m = w.alloc(14);
let m2 = w.realloc(m, 16);
T.assert(m === m2/* because of alignment */);
T.assert(0 === w.realloc(m, 0));
m = m2 = 0;
// Check allocation limits and allocator's responses...
T.assert('number' === typeof sqlite3.capi.SQLITE_MAX_ALLOCATION_SIZE);
if(!sqlite3.config.useStdAlloc){
const tooMuch = sqlite3.capi.SQLITE_MAX_ALLOCATION_SIZE + 1,
isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError;
T.mustThrowMatching(()=>w.alloc(tooMuch), isAllocErr)
.assert(0 === w.alloc.impl(tooMuch))
.mustThrowMatching(()=>w.realloc(0, tooMuch), isAllocErr)
.assert(0 === w.realloc.impl(0, tooMuch));
}
// Check allocFromTypedArray()...
const byteList = [11,22,33]
const u = new Uint8Array(byteList);
m = w.allocFromTypedArray(u);
for(let i = 0; i < u.length; ++i){
T.assert(u[i] === byteList[i])
.assert(u[i] === w.peek8(m + i));
}
w.dealloc(m);
m = w.allocFromTypedArray(u.buffer);
for(let i = 0; i < u.length; ++i){
T.assert(u[i] === byteList[i])
.assert(u[i] === w.peek8(m + i));
}
w.dealloc(m);
T.mustThrowMatching(
()=>w.allocFromTypedArray(1),
'Value is not of a supported TypedArray type.'
);
}
{ // Test peekXYZ()/pokeXYZ()...
const m = w.alloc(8);
T.assert( 17 === w.poke8(m,17).peek8(m) )
.assert( 31987 === w.poke16(m,31987).peek16(m) )
.assert( 345678 === w.poke32(m,345678).peek32(m) )
.assert(
T.eqApprox( 345678.9, w.poke32f(m,345678.9).peek32f(m) )
).assert(
T.eqApprox( 4567890123.4, w.poke64f(m, 4567890123.4).peek64f(m) )
);
if(w.bigIntEnabled){
T.assert(
BigInt(Number.MAX_SAFE_INTEGER) ===
w.poke64(m, Number.MAX_SAFE_INTEGER).peek64(m)
);
}
w.dealloc(m);
}
// isPtr32()
{
const ip = w.isPtr32;
T.assert(ip(0))
.assert(!ip(-1))
.assert(!ip(1.1))
.assert(!ip(0xffffffff))
.assert(ip(0x7fffffff))
.assert(!ip())
.assert(!ip(null)/*might change: under consideration*/)
;
}
//log("jstrlen()...");
{
T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc"));
}
//log("jstrcpy()...");
{
const fillChar = 10;
let ua = new Uint8Array(8), rc,
refill = ()=>ua.fill(fillChar);
refill();
rc = w.jstrcpy("hello", ua);
T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]);
refill();
ua[5] = chr('!');
rc = w.jstrcpy("HELLO", ua, 0, -1, false);
T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]);
refill();
rc = w.jstrcpy("the end", ua, 4);
//log("rc,ua",rc,ua);
T.assert(4===rc).assert(0===ua[7]).
assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
refill();
rc = w.jstrcpy("the end", ua, 4, -1, false);
T.assert(4===rc).assert(chr(' ')===ua[7]).
assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
refill();
rc = w.jstrcpy("", ua, 0, 1, true);
//log("rc,ua",rc,ua);
T.assert(1===rc).assert(0===ua[0]);
refill();
rc = w.jstrcpy("x", ua, 0, 1, true);
//log("rc,ua",rc,ua);
T.assert(1===rc).assert(0===ua[0]);
refill();
rc = w.jstrcpy('äbä', ua, 0, 1, true);
T.assert(1===rc, 'Must not write partial multi-byte char.')
.assert(0===ua[0]);
refill();
rc = w.jstrcpy('äbä', ua, 0, 2, true);
T.assert(1===rc, 'Must not write partial multi-byte char.')
.assert(0===ua[0]);
refill();
rc = w.jstrcpy('äbä', ua, 0, 2, false);
T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]);
}/*jstrcpy()*/
//log("cstrncpy()...");
{
const scope = w.scopedAllocPush();
try {
let cStr = w.scopedAllocCString("hello");
const n = w.cstrlen(cStr);
let cpy = w.scopedAlloc(n+10);
let rc = w.cstrncpy(cpy, cStr, n+10);
T.assert(n+1 === rc).
assert("hello" === w.cstrToJs(cpy)).
assert(chr('o') === w.peek8(cpy+n-1)).
assert(0 === w.peek8(cpy+n));
let cStr2 = w.scopedAllocCString("HI!!!");
rc = w.cstrncpy(cpy, cStr2, 3);
T.assert(3===rc).
assert("HI!lo" === w.cstrToJs(cpy)).
assert(chr('!') === w.peek8(cpy+2)).
assert(chr('l') === w.peek8(cpy+3));
}finally{
w.scopedAllocPop(scope);
}
}
//log("jstrToUintArray()...");
{
let a = w.jstrToUintArray("hello", false);
T.assert(5===a.byteLength).assert(chr('o')===a[4]);
a = w.jstrToUintArray("hello", true);
T.assert(6===a.byteLength).assert(chr('o')===a[4]).assert(0===a[5]);
a = w.jstrToUintArray("äbä", false);
T.assert(5===a.byteLength).assert(chr('b')===a[2]);
a = w.jstrToUintArray("äbä", true);
T.assert(6===a.byteLength).assert(chr('b')===a[2]).assert(0===a[5]);
}
//log("allocCString()...");
{
const jstr = "hällo, world!";
const [cstr, n] = w.allocCString(jstr, true);
T.assert(14 === n)
.assert(0===w.peek8(cstr+n))
.assert(chr('!')===w.peek8(cstr+n-1));
w.dealloc(cstr);
}
//log("scopedAlloc() and friends...");
{
const alloc = w.alloc, dealloc = w.dealloc;
w.alloc = w.dealloc = null;
T.assert(!w.scopedAlloc.level)
.mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
w.alloc = alloc;
T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
w.dealloc = dealloc;
T.mustThrowMatching(()=>w.scopedAllocPop(), /^Invalid state/)
.mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
.mustThrowMatching(()=>w.scopedAlloc.level=0, /read-only/);
const asc = w.scopedAllocPush();
let asc2;
try {
const p1 = w.scopedAlloc(16),
p2 = w.scopedAlloc(16);
T.assert(1===w.scopedAlloc.level)
.assert(Number.isFinite(p1))
.assert(Number.isFinite(p2))
.assert(asc[0] === p1)
.assert(asc[1]===p2);
asc2 = w.scopedAllocPush();
const p3 = w.scopedAlloc(16);
T.assert(2===w.scopedAlloc.level)
.assert(Number.isFinite(p3))
.assert(2===asc.length)
.assert(p3===asc2[0]);
const [z1, z2, z3] = w.scopedAllocPtr(3);
T.assert('number'===typeof z1).assert(z2>z1).assert(z3>z2)
.assert(0===w.peek32(z1), 'allocPtr() must zero the targets')
.assert(0===w.peek32(z3));
}finally{
// Pop them in "incorrect" order to make sure they behave:
w.scopedAllocPop(asc);
T.assert(0===asc.length);
T.mustThrowMatching(()=>w.scopedAllocPop(asc),
/^Invalid state object/);
if(asc2){
T.assert(2===asc2.length,'Should be p3 and z1');
w.scopedAllocPop(asc2);
T.assert(0===asc2.length);
T.mustThrowMatching(()=>w.scopedAllocPop(asc2),
/^Invalid state object/);
}
}
T.assert(0===w.scopedAlloc.level);
w.scopedAllocCall(function(){
T.assert(1===w.scopedAlloc.level);
const [cstr, n] = w.scopedAllocCString("hello, world", true);
T.assert(12 === n)
.assert(0===w.peek8(cstr+n))
.assert(chr('d')===w.peek8(cstr+n-1));
});
}/*scopedAlloc()*/
//log("xCall()...");
{
const pJson = w.xCall('sqlite3_wasm_enum_json');
T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
}
//log("xWrap()...");
{
T.mustThrowMatching(()=>w.xWrap('sqlite3_libversion',null,'i32'),
/requires 0 arg/).
assert(w.xWrap.resultAdapter('i32') instanceof Function).
assert(w.xWrap.argAdapter('i32') instanceof Function);
let fw = w.xWrap('sqlite3_libversion','utf8');
T.mustThrowMatching(()=>fw(1), /requires 0 arg/);
let rc = fw();
T.assert('string'===typeof rc).assert(rc.length>5);
rc = w.xCallWrapped('sqlite3_wasm_enum_json','*');
T.assert(rc>0 && Number.isFinite(rc));
rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8');
T.assert('string'===typeof rc).assert(rc.length>300);
{ // 'string:static' argAdapter() sanity checks...
let argAd = w.xWrap.argAdapter('string:static');
let p0 = argAd('foo'), p1 = argAd('bar');
T.assert(w.isPtr(p0) && w.isPtr(p1))
.assert(p0 !== p1)
.assert(p0 === argAd('foo'))
.assert(p1 === argAd('bar'));
}
// 'string:flexible' argAdapter() sanity checks...
w.scopedAllocCall(()=>{
const argAd = w.xWrap.argAdapter('string:flexible');
const cj = (v)=>w.cstrToJs(argAd(v));
T.assert('Hi' === cj('Hi'))
.assert('hi' === cj(['h','i']))
.assert('HI' === cj(new Uint8Array([72, 73])));
});
// jsFuncToWasm()
{
const fsum3 = (x,y,z)=>x+y+z;
fw = w.jsFuncToWasm('i(iii)', fsum3);
T.assert(fw instanceof Function)
.assert( fsum3 !== fw )
.assert( 3 === fw.length )
.assert( 6 === fw(1,2,3) );
T.mustThrowMatching( ()=>w.jsFuncToWasm('x()', function(){}),
'Invalid signature letter: x');
}
// xWrap(Function,...)
{
let fp;
try {
const fmy = function fmy(i,s,d){
if(fmy.debug) log("fmy(",...arguments,")");
T.assert( 3 === i )
.assert( w.isPtr(s) )
.assert( w.cstrToJs(s) === 'a string' )
.assert( T.eqApprox(1.2, d) );
return w.allocCString("hi");
};
fmy.debug = false;
const xwArgs = ['string:dealloc', ['i32', 'string', 'f64']];
fw = w.xWrap(fmy, ...xwArgs);
const fmyArgs = [3, 'a string', 1.2];
let rc = fw(...fmyArgs);
T.assert( 'hi' === rc );
if(0){
/* Retain this as a "reminder to self"...
This extra level of indirection does not work: the
string argument is ending up as a null in fmy() but
the numeric arguments are making their ways through
What's happening is: installFunction() is creating a
WASM-compatible function instance. When we pass a JS string
into there it's getting coerced into `null` before being passed
on to the lower-level wrapper.
*/
fmy.debug = true;
fp = wasm.installFunction('i(isd)', fw);
fw = w.functionEntry(fp);
rc = fw(...fmyArgs);
log("rc =",rc);
T.assert( 'hi' === rc );
// Similarly, this does not work:
//let fpw = w.xWrap(fp, null, [null,null,null]);
//rc = fpw(...fmyArgs);
//log("rc =",rc);
//T.assert( 'hi' === rc );
}
}finally{
wasm.uninstallFunction(fp);
}
}
if(haveWasmCTests()){
if(!sqlite3.config.useStdAlloc){
fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:dealloc',['i32']);
rc = fw(0);
T.assert('hello'===rc);
rc = fw(1);
T.assert(null===rc);
}
if(w.bigIntEnabled){
w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v));
w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v));
fw = w.xWrap('sqlite3_wasm_test_int64_times2','thrice','twice');
rc = fw(1);
T.assert(12n===rc);
w.scopedAllocCall(function(){
const pI1 = w.scopedAlloc(8), pI2 = pI1+4;
w.pokePtr([pI1, pI2], 0);
const f = w.xWrap('sqlite3_wasm_test_int64_minmax',undefined,['i64*','i64*']);
const [r1, r2] = w.peek64([pI1, pI2]);
T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2));
});
}
}
}/*xWrap()*/
}/*WhWasmUtil*/)
////////////////////////////////////////////////////////////////////
.t('sqlite3.StructBinder (jaccwabyt🐇)', function(sqlite3){
const S = sqlite3, W = S.wasm;
const MyStructDef = {
sizeof: 16,
members: {
p4: {offset: 0, sizeof: 4, signature: "i"},
pP: {offset: 4, sizeof: 4, signature: "P"},
ro: {offset: 8, sizeof: 4, signature: "i", readOnly: true},
cstr: {offset: 12, sizeof: 4, signature: "s"}
}
};
if(W.bigIntEnabled){
const m = MyStructDef;
m.members.p8 = {offset: m.sizeof, sizeof: 8, signature: "j"};
m.sizeof += m.members.p8.sizeof;
}
const StructType = S.StructBinder.StructType;
const K = S.StructBinder('my_struct',MyStructDef);
T.mustThrowMatching(()=>K(), /via 'new'/).
mustThrowMatching(()=>new K('hi'), /^Invalid pointer/);
const k1 = new K(), k2 = new K();
try {
T.assert(k1.constructor === K).
assert(K.isA(k1)).
assert(k1 instanceof K).
assert(K.prototype.lookupMember('p4').key === '$p4').
assert(K.prototype.lookupMember('$p4').name === 'p4').
mustThrowMatching(()=>K.prototype.lookupMember('nope'), /not a mapped/).
assert(undefined === K.prototype.lookupMember('nope',false)).
assert(k1 instanceof StructType).
assert(StructType.isA(k1)).
mustThrowMatching(()=>k1.$ro = 1, /read-only/);
Object.keys(MyStructDef.members).forEach(function(key){
key = K.memberKey(key);
T.assert(0 == k1[key],
"Expecting allocation to zero the memory "+
"for "+key+" but got: "+k1[key]+
" from "+k1.memoryDump());
});
T.assert('number' === typeof k1.pointer).
mustThrowMatching(()=>k1.pointer = 1, /pointer/);
k1.$p4 = 1; k1.$pP = 2;
T.assert(1 === k1.$p4).assert(2 === k1.$pP);
if(MyStructDef.members.$p8){
k1.$p8 = 1/*must not throw despite not being a BigInt*/;
k1.$p8 = BigInt(Number.MAX_SAFE_INTEGER * 2);
T.assert(BigInt(2 * Number.MAX_SAFE_INTEGER) === k1.$p8);
}
T.assert(!k1.ondispose);
k1.setMemberCString('cstr', "A C-string.");
T.assert(Array.isArray(k1.ondispose)).
assert(k1.ondispose[0] === k1.$cstr).
assert('number' === typeof k1.$cstr).
assert('A C-string.' === k1.memberToJsString('cstr'));
k1.$pP = k2;
T.assert(k1.$pP === k2.pointer);
k1.$pP = null/*null is special-cased to 0.*/;
T.assert(0===k1.$pP);
let ptr = k1.pointer;
k1.dispose();
T.assert(undefined === k1.pointer).
mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
}finally{
k1.dispose();
k2.dispose();
}
if(!W.bigIntEnabled){
log("Skipping WasmTestStruct tests: BigInt not enabled.");
return;
}
const WTStructDesc =
W.ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0];
const autoResolvePtr = true /* EXPERIMENTAL */;
if(autoResolvePtr){
WTStructDesc.members.ppV.signature = 'P';
}
const WTStruct = S.StructBinder(WTStructDesc);
//log(WTStruct.structName, WTStruct.structInfo);
const wts = new WTStruct();
//log("WTStruct.prototype keys:",Object.keys(WTStruct.prototype));
try{
T.assert(wts.constructor === WTStruct).
assert(WTStruct.memberKeys().indexOf('$ppV')>=0).
assert(wts.memberKeys().indexOf('$v8')>=0).
assert(!K.isA(wts)).
assert(WTStruct.isA(wts)).
assert(wts instanceof WTStruct).
assert(wts instanceof StructType).
assert(StructType.isA(wts)).
assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
assert(0===wts.$ppV).assert(0===wts.$xFunc);
const testFunc =
W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/);
let counter = 0;
//log("wts.pointer =",wts.pointer);
const wtsFunc = function(arg){
/*log("This from a JS function called from C, "+
"which itself was called from JS. arg =",arg);*/
++counter;
if(3===counter){
tossQuietly("Testing exception propagation.");
}
}
wts.$v4 = 10; wts.$v8 = 20;
wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc'))
T.assert(0===counter).assert(10 === wts.$v4).assert(20n === wts.$v8)
.assert(0 === wts.$ppV).assert('number' === typeof wts.$xFunc)
.assert(0 === wts.$cstr)
.assert(wts.memberIsString('$cstr'))
.assert(!wts.memberIsString('$v4'))
.assert(null === wts.memberToJsString('$cstr'))
.assert(W.functionEntry(wts.$xFunc) instanceof Function);
/* It might seem silly to assert that the values match
what we just set, but recall that all of those property
reads and writes are, via property interceptors,
actually marshaling their data to/from a raw memory
buffer, so merely reading them back is actually part of
testing the struct-wrapping API. */
testFunc(wts.pointer);
//log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV);
T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8)
.assert(wts.$ppV === wts.pointer)
.assert('string' === typeof wts.memberToJsString('cstr'))
.assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr'))
.mustThrowMatching(()=>wts.memberToJsString('xFunc'),
/Invalid member type signature for C-string/)
;
testFunc(wts.pointer);
T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8)
.assert(wts.$ppV === wts.pointer);
/** The 3rd call to wtsFunc throw from JS, which is called
from C, which is called from JS. Let's ensure that
that exception propagates back here... */
T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/);
W.uninstallFunction(wts.$xFunc);
wts.$xFunc = 0;
wts.$ppV = 0;
T.assert(!wts.$ppV);
//WTStruct.debugFlags(0x03);
wts.$ppV = wts;
T.assert(wts.pointer === wts.$ppV)
wts.setMemberCString('cstr', "A C-string.");
T.assert(Array.isArray(wts.ondispose)).
assert(wts.ondispose[0] === wts.$cstr).
assert('A C-string.' === wts.memberToJsString('cstr'));
const ptr = wts.pointer;
wts.dispose();
T.assert(ptr).assert(undefined === wts.pointer);
}finally{
wts.dispose();
}
if(1){ // ondispose of other struct instances
const s1 = new WTStruct, s2 = new WTStruct, s3 = new WTStruct;
T.assert(s1.lookupMember instanceof Function)
.assert(s1.addOnDispose instanceof Function);
s1.addOnDispose(s2,"testing variadic args");
T.assert(2===s1.ondispose.length);
s2.addOnDispose(s3);
s1.dispose();
T.assert(!s2.pointer,"Expecting s2 to be ondispose'd by s1.");
T.assert(!s3.pointer,"Expecting s3 to be ondispose'd by s2.");
}
}/*StructBinder*/)
////////////////////////////////////////////////////////////////////
.t('sqlite3.wasm.pstack', function(sqlite3){
const P = wasm.pstack;
const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError;
const stack = P.pointer;
T.assert(0===stack % 8 /* must be 8-byte aligned */);
try{
const remaining = P.remaining;
T.assert(P.quota >= 4096)
.assert(remaining === P.quota)
.mustThrowMatching(()=>P.alloc(0), isAllocErr)
.mustThrowMatching(()=>P.alloc(-1), isAllocErr)
.mustThrowMatching(
()=>P.alloc('i33'),
(e)=>e instanceof sqlite3.WasmAllocError
);
;
let p1 = P.alloc(12);
T.assert(p1 === stack - 16/*8-byte aligned*/)
.assert(P.pointer === p1);
let p2 = P.alloc(7);
T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/)
.mustThrowMatching(()=>P.alloc(remaining), isAllocErr)
.assert(24 === stack - p2)
.assert(P.pointer === p2);
let n = remaining - (stack - p2);
let p3 = P.alloc(n);
T.assert(p3 === stack-remaining)
.mustThrowMatching(()=>P.alloc(1), isAllocErr);
}finally{
P.restore(stack);
}
T.assert(P.pointer === stack);
try {
const [p1, p2, p3] = P.allocChunks(3,'i32');
T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/)
.assert(p2 === p1 + 4)
.assert(p3 === p2 + 4);
T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16),
(e)=>e instanceof sqlite3.WasmAllocError)
}finally{
P.restore(stack);
}
T.assert(P.pointer === stack);
try {
let [p1, p2, p3] = P.allocPtr(3,false);
let sPos = stack-16/*always rounded to multiple of 8*/;
T.assert(P.pointer === sPos)
.assert(p2 === p1 + 4)
.assert(p3 === p2 + 4);
[p1, p2, p3] = P.allocPtr(3);
T.assert(P.pointer === sPos-24/*3 x 8 bytes*/)
.assert(p2 === p1 + 8)
.assert(p3 === p2 + 8);
p1 = P.allocPtr();
T.assert('number'===typeof p1);
}finally{
P.restore(stack);
}
}/*pstack tests*/)
////////////////////////////////////////////////////////////////////
;/*end of C/WASM utils checks*/
T.g('sqlite3_randomness()')
.t('To memory buffer', function(sqlite3){
const stack = wasm.pstack.pointer;
try{
const n = 520;
const p = wasm.pstack.alloc(n);
T.assert(0===wasm.peek8(p))
.assert(0===wasm.peek8(p+n-1));
T.assert(undefined === capi.sqlite3_randomness(n - 10, p));
let j, check = 0;
const heap = wasm.heap8u();
for(j = 0; j < 10 && 0===check; ++j){
check += heap[p + j];
}
T.assert(check > 0);
check = 0;
// Ensure that the trailing bytes were not modified...
for(j = n - 10; j < n && 0===check; ++j){
check += heap[p + j];
}
T.assert(0===check);
}finally{
wasm.pstack.restore(stack);
}
})
.t('To byte array', function(sqlite3){
const ta = new Uint8Array(117);
let i, n = 0;
for(i=0; i<ta.byteLength && 0===n; ++i){
n += ta[i];
}
T.assert(0===n)
.assert(ta === capi.sqlite3_randomness(ta));
for(i=ta.byteLength-10; i<ta.byteLength && 0===n; ++i){
n += ta[i];
}
T.assert(n>0);
const t0 = new Uint8Array(0);
T.assert(t0 === capi.sqlite3_randomness(t0),
"0-length array is a special case");
})
;/*end sqlite3_randomness() checks*/
////////////////////////////////////////////////////////////////////////
T.g('sqlite3.oo1')
.t('Create db', function(sqlite3){
const dbFile = '/tester1.db';
wasm.sqlite3_wasm_vfs_unlink(0, dbFile);
const db = this.db = new sqlite3.oo1.DB(dbFile, 0 ? 'ct' : 'c');
db.onclose = {
disposeAfter: [],
disposeBefore: [
(db)=>{
//console.debug("db.onclose.before dropping modules");
//sqlite3.capi.sqlite3_drop_modules(db.pointer, 0);
}
],
before: function(db){
while(this.disposeBefore.length){
const v = this.disposeBefore.shift();
console.debug("db.onclose.before cleaning up:",v);
if(wasm.isPtr(v)) wasm.dealloc(v);
else if(v instanceof sqlite3.StructBinder.StructType){
v.dispose();
}else if(v instanceof Function){
try{ v(db) } catch(e){
console.warn("beforeDispose() callback threw:",e);
}
}
}
},
after: function(){
while(this.disposeAfter.length){
const v = this.disposeAfter.shift();
console.debug("db.onclose.after cleaning up:",v);
if(wasm.isPtr(v)) wasm.dealloc(v);
else if(v instanceof sqlite3.StructBinder.StructType){
v.dispose();
}else if(v instanceof Function){
try{v()} catch(e){/*ignored*/}
}
}
}
};
T.assert(wasm.isPtr(db.pointer))
.mustThrowMatching(()=>db.pointer=1, /read-only/)
.assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1))
.assert('main'===db.dbName(0))
.assert('string' === typeof db.dbVfsName())
.assert(db.pointer === wasm.xWrap.testConvertArg('sqlite3*',db));
// Custom db error message handling via sqlite3_prepare_v2/v3()
let rc = capi.sqlite3_prepare_v3(db.pointer, {/*invalid*/}, -1, 0, null, null);
T.assert(capi.SQLITE_MISUSE === rc)
.assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL"))
.assert(dbFile === db.dbFilename())
.assert(!db.dbFilename('nope'));
//Sanity check DB.checkRc()...
let ex;
try{db.checkRc(rc)}
catch(e){ex = e}
T.assert(ex instanceof sqlite3.SQLite3Error)
.assert(capi.SQLITE_MISUSE===ex.resultCode)
.assert(0===ex.message.indexOf("SQLITE_MISUSE: sqlite3 result code"))
.assert(ex.message.indexOf("Invalid SQL")>0);
T.assert(db === db.checkRc(0))
.assert(db === sqlite3.oo1.DB.checkRc(db,0))
.assert(null === sqlite3.oo1.DB.checkRc(null,0));
this.progressHandlerCount = 0;
capi.sqlite3_progress_handler(db, 5, (p)=>{
++this.progressHandlerCount;
return 0;
}, 0);
})
////////////////////////////////////////////////////////////////////
.t('sqlite3_db_config() and sqlite3_db_status()', function(sqlite3){
let rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, 0, 0);
T.assert(0===rc);
rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAX+1, 0);
T.assert(capi.SQLITE_MISUSE === rc);
rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAINDBNAME, "main");
T.assert(0 === rc);
const stack = wasm.pstack.pointer;
try {
const [pCur, pHi] = wasm.pstack.allocChunks(2,'i64');
wasm.poke32([pCur, pHi], 0);
let [vCur, vHi] = wasm.peek32(pCur, pHi);
T.assert(0===vCur).assert(0===vHi);
rc = capi.sqlite3_status(capi.SQLITE_STATUS_MEMORY_USED,
pCur, pHi, 0);
[vCur, vHi] = wasm.peek32(pCur, pHi);
//console.warn("i32 vCur,vHi",vCur,vHi);
T.assert(0 === rc).assert(vCur > 0).assert(vHi >= vCur);
if(wasm.bigIntEnabled){
// Again in 64-bit. Recall that pCur and pHi are allocated
// large enough to account for this re-use.
wasm.poke64([pCur, pHi], 0);
rc = capi.sqlite3_status64(capi.SQLITE_STATUS_MEMORY_USED,
pCur, pHi, 0);
[vCur, vHi] = wasm.peek64([pCur, pHi]);
//console.warn("i64 vCur,vHi",vCur,vHi);
T.assert(0 === rc).assert(vCur > 0).assert(vHi >= vCur);
}
}finally{
wasm.pstack.restore(stack);
}
})
////////////////////////////////////////////////////////////////////
.t('DB.Stmt', function(sqlite3){
let st = this.db.prepare(
new TextEncoder('utf-8').encode("select 3 as a")
);
//debug("statement =",st);
this.progressHandlerCount = 0;
let rc;
try {
T.assert(wasm.isPtr(st.pointer))
.mustThrowMatching(()=>st.pointer=1, /read-only/)
.assert(1===this.db.openStatementCount())
.assert(
capi.sqlite3_stmt_status(
st, capi.SQLITE_STMTSTATUS_RUN, 0
) === 0)
.assert(!st._mayGet)
.assert('a' === st.getColumnName(0))
.mustThrowMatching(()=>st.columnCount=2,
/columnCount property is read-only/)
.assert(1===st.columnCount)
.assert(0===st.parameterCount)
.mustThrow(()=>st.bind(1,null))
.assert(true===st.step())
.assert(3 === st.get(0))
.mustThrow(()=>st.get(1))
.mustThrow(()=>st.get(0,~capi.SQLITE_INTEGER))
.assert(3 === st.get(0,capi.SQLITE_INTEGER))
.assert(3 === st.getInt(0))
.assert('3' === st.get(0,capi.SQLITE_TEXT))
.assert('3' === st.getString(0))
.assert(3.0 === st.get(0,capi.SQLITE_FLOAT))
.assert(3.0 === st.getFloat(0))
.assert(3 === st.get({}).a)
.assert(3 === st.get([])[0])
.assert(3 === st.getJSON(0))
.assert(st.get(0,capi.SQLITE_BLOB) instanceof Uint8Array)
.assert(1===st.get(0,capi.SQLITE_BLOB).length)
.assert(st.getBlob(0) instanceof Uint8Array)
.assert('3'.charCodeAt(0) === st.getBlob(0)[0])
.assert(st._mayGet)
.assert(false===st.step())
.assert(!st._mayGet)
.assert(
capi.sqlite3_stmt_status(
st, capi.SQLITE_STMTSTATUS_RUN, 0
) > 0);
T.assert(this.progressHandlerCount > 0,
"Expecting progress callback.").
assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
}finally{
rc = st.finalize();
}
T.assert(!st.pointer)
.assert(0===this.db.openStatementCount())
.assert(0===rc);
T.mustThrowMatching(()=>new sqlite3.oo1.Stmt("hi"), function(err){
return (err instanceof sqlite3.SQLite3Error)
&& capi.SQLITE_MISUSE === err.resultCode
&& 0 < err.message.indexOf("Do not call the Stmt constructor directly.")
});
})
////////////////////////////////////////////////////////////////////////
.t('sqlite3_js_...()', function(){
const db = this.db;
if(1){
const vfsList = capi.sqlite3_js_vfs_list();
T.assert(vfsList.length>1);
wasm.scopedAllocCall(()=>{
const vfsArg = (v)=>wasm.xWrap.testConvertArg('sqlite3_vfs*',v);
for(const v of vfsList){
T.assert('string' === typeof v);
const pVfs = capi.sqlite3_vfs_find(v);
T.assert(wasm.isPtr(pVfs))
.assert(pVfs===vfsArg(v));
const vfs = new capi.sqlite3_vfs(pVfs);
try { T.assert(vfsArg(vfs)===pVfs) }
finally{ vfs.dispose() }
}
});
}
/**
Trivia: the magic db name ":memory:" does not actually use the
"memdb" VFS unless "memdb" is _explicitly_ provided as the VFS
name. Instead, it uses the default VFS with an in-memory btree.
Thus this.db's VFS may not be memdb even though it's an in-memory
db.
*/
const pVfsMem = capi.sqlite3_vfs_find('memdb'),
pVfsDflt = capi.sqlite3_vfs_find(0),
pVfsDb = capi.sqlite3_js_db_vfs(db.pointer);
T.assert(pVfsMem > 0)
.assert(pVfsDflt > 0)
.assert(pVfsDb > 0)
.assert(pVfsMem !== pVfsDflt
/* memdb lives on top of the default vfs */)
.assert(pVfsDb === pVfsDflt || pVfsdb === pVfsMem)
;
/*const vMem = new capi.sqlite3_vfs(pVfsMem),
vDflt = new capi.sqlite3_vfs(pVfsDflt),
vDb = new capi.sqlite3_vfs(pVfsDb);*/
const duv = capi.sqlite3_js_db_uses_vfs;
T.assert(pVfsDflt === duv(db.pointer, 0)
|| pVfsMem === duv(db.pointer,0))
.assert(!duv(db.pointer, "foo"))
;
}/*sqlite3_js_...()*/)
////////////////////////////////////////////////////////////////////
.t('Table t', function(sqlite3){
const db = this.db;
let list = [];
this.progressHandlerCount = 0;
let rc = db.exec({
sql:['CREATE TABLE t(a,b);',
// ^^^ using TEMP TABLE breaks the db export test
"INSERT INTO t(a,b) VALUES(1,2),(3,4),",
"(?,?)"/*intentionally missing semicolon to test for
off-by-one bug in string-to-WASM conversion*/],
saveSql: list,
bind: [5,6]
});
//debug("Exec'd SQL:", list);
T.assert(rc === db)
.assert(2 === list.length)
.assert('string'===typeof list[1])
.assert(3===db.changes())
.assert(this.progressHandlerCount > 0,
"Expecting progress callback.")
if(wasm.bigIntEnabled){
T.assert(3n===db.changes(false,true));
}
rc = db.exec({
sql: "INSERT INTO t(a,b) values('blob',X'6869') RETURNING 13",
rowMode: 0
});
T.assert(Array.isArray(rc))
.assert(1===rc.length)
.assert(13 === rc[0])
.assert(1===db.changes());
let vals = db.selectValues('select a from t order by a limit 2');
T.assert( 2 === vals.length )
.assert( 1===vals[0] && 3===vals[1] );
vals = db.selectValues('select a from t order by a limit $L',
{$L:2}, capi.SQLITE_TEXT);
T.assert( 2 === vals.length )
.assert( '1'===vals[0] && '3'===vals[1] );
vals = undefined;
let blob = db.selectValue("select b from t where a='blob'");
T.assert(blob instanceof Uint8Array).
assert(0x68===blob[0] && 0x69===blob[1]);
blob = null;
let counter = 0, colNames = [];
list.length = 0;
db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{
rowMode: 'object',
resultRows: list,
columnNames: colNames,
_myState: 3 /* Accessible from the callback */,
callback: function(row,stmt){
++counter;
T.assert(
3 === this._myState
/* Recall that "this" is the options object. */
).assert(
this.columnNames===colNames
).assert(
this.columnNames[0]==='a' && this.columnNames[1]==='b'
).assert(
(row.a%2 && row.a<6) || 'blob'===row.a
);
}
});
T.assert(2 === colNames.length)
.assert('a' === colNames[0])
.assert(4 === counter)
.assert(4 === list.length);
colNames = [];
db.exec({
/* Ensure that columnNames is populated for empty result sets. */
sql: "SELECT a a, b B FROM t WHERE 0",
columnNames: colNames
});
T.assert(2===colNames.length)
.assert('a'===colNames[0] && 'B'===colNames[1]);
list.length = 0;
db.exec("SELECT a a, b b FROM t",{
rowMode: 'array',
callback: function(row,stmt){
++counter;
T.assert(Array.isArray(row))
.assert((0===row[1]%2 && row[1]<7)
|| (row[1] instanceof Uint8Array));
}
});
T.assert(8 === counter);
T.assert(Number.MIN_SAFE_INTEGER ===
db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)).
assert(Number.MAX_SAFE_INTEGER ===
db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER));
counter = 0;
let rv = db.exec({
sql: "SELECT a FROM t",
callback: ()=>(1===++counter),
});
T.assert(db === rv)
.assert(2===counter,
"Expecting exec step() loop to stop if callback returns false.");
/** If exec() is passed neither callback nor returnValue but
is passed an explicit rowMode then the default returnValue
is the whole result set, as if an empty resultRows option
had been passed. */
rv = db.exec({
sql: "SELECT -1 UNION ALL SELECT -2 UNION ALL SELECT -3 ORDER BY 1 DESC",
rowMode: 0
});
T.assert(Array.isArray(rv)).assert(3===rv.length)
.assert(-1===rv[0]).assert(-3===rv[2]);
rv = db.exec("SELECT 1 WHERE 0",{rowMode: 0});
T.assert(Array.isArray(rv)).assert(0===rv.length);
if(wasm.bigIntEnabled && haveWasmCTests()){
const mI = wasm.xCall('sqlite3_wasm_test_int64_max');
const b = BigInt(Number.MAX_SAFE_INTEGER * 2);
T.assert(b === db.selectValue("SELECT "+b)).
assert(b === db.selectValue("SELECT ?", b)).
assert(mI == db.selectValue("SELECT $x", {$x:mI}));
}else{
/* Curiously, the JS spec seems to be off by one with the definitions
of MIN/MAX_SAFE_INTEGER:
https://github.com/emscripten-core/emscripten/issues/17391 */
T.mustThrow(()=>db.selectValue("SELECT "+(Number.MAX_SAFE_INTEGER+1))).
mustThrow(()=>db.selectValue("SELECT "+(Number.MIN_SAFE_INTEGER-1)));
}
let st = db.prepare("update t set b=:b where a='blob'");
try {
T.assert(0===st.columnCount);
const ndx = st.getParamIndex(':b');
T.assert(1===ndx);
st.bindAsBlob(ndx, "ima blob")
/*step() skipped intentionally*/.reset(true);
} finally {
T.assert(0===st.finalize())
.assert(undefined===st.finalize());
}
try {
db.prepare("/*empty SQL*/");
toss("Must not be reached.");
}catch(e){
T.assert(e instanceof sqlite3.SQLite3Error)
.assert(0==e.message.indexOf('Cannot prepare empty'));
}
counter = 0;
db.exec({
// Check for https://sqlite.org/forum/forumpost/895425b49a
sql: "pragma table_info('t')",
rowMode: 'object',
callback: function(row){
++counter;
T.assert(row.name==='a' || row.name==='b');
}
});
T.assert(2===counter);
})/*setup table T*/
////////////////////////////////////////////////////////////////////
.t({
name: "sqlite3_set_authorizer()",
test:function(sqlite3){
T.assert(capi.SQLITE_IGNORE>0)
.assert(capi.SQLITE_DENY>0);
const db = this.db;
const ssa = capi.sqlite3_set_authorizer;
const n = db.selectValue('select count(*) from t');
T.assert(n>0);
let authCount = 0;
let rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){
++authCount;
return capi.SQLITE_IGNORE;
}, 0);
T.assert(0===rc)
.assert(
undefined === db.selectValue('select count(*) from t')
/* Note that the count() never runs, so we get undefined
instead of 0. */
)
.assert(authCount>0);
authCount = 0;
db.exec("update t set a=-9999");
T.assert(authCount>0);
/* Reminder: we don't use DELETE because, from the C API docs:
"If the action code is [SQLITE_DELETE] and the callback
returns [SQLITE_IGNORE] then the [DELETE] operation proceeds
but the [truncate optimization] is disabled and all rows are
deleted individually."
*/
rc = ssa(db, null, 0);
authCount = 0;
T.assert(-9999 != db.selectValue('select a from t'))
.assert(0===authCount);
rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){
++authCount;
return capi.SQLITE_DENY;
}, 0);
T.assert(0===rc);
let err;
try{ db.exec("select 1 from t") }
catch(e){ err = e }
T.assert(err instanceof sqlite3.SQLite3Error)
.assert(err.message.indexOf('not authorized'>0))
.assert(1===authCount);
authCount = 0;
rc = ssa(db, function(...args){
++authCount;
return capi.SQLITE_OK;
}, 0);
T.assert(0===rc);
T.assert(n === db.selectValue('select count(*) from t'))
.assert(authCount>0);
authCount = 0;
rc = ssa(db, function(pV, iCode, s0, s1, s2, s3){
++authCount;
throw new Error("Testing catching of authorizer.");
}, 0);
T.assert(0===rc);
authCount = 0;
err = undefined;
try{ db.exec("select 1 from t") }
catch(e){err = e}
T.assert(err instanceof Error)
.assert(err.message.indexOf('not authorized')>0)
/* Note that the thrown message is trumped/overwritten
by the authorizer process. */
.assert(1===authCount);
rc = ssa(db, 0, 0);
authCount = 0;
T.assert(0===rc);
T.assert(n === db.selectValue('select count(*) from t'))
.assert(0===authCount);
}
})/*sqlite3_set_authorizer()*/
////////////////////////////////////////////////////////////////////////
.t("sqlite3_table_column_metadata()", function(sqlite3){
const stack = wasm.pstack.pointer;
try{
const [pzDT, pzColl, pNotNull, pPK, pAuto] =
wasm.pstack.allocPtr(5);
const rc = capi.sqlite3_table_column_metadata(
this.db, "main", "t", "rowid",
pzDT, pzColl, pNotNull, pPK, pAuto
);
T.assert(0===rc)
.assert("INTEGER"===wasm.cstrToJs(wasm.peekPtr(pzDT)))
.assert("BINARY"===wasm.cstrToJs(wasm.peekPtr(pzColl)))
.assert(0===wasm.peek32(pNotNull))
.assert(1===wasm.peek32(pPK))
.assert(0===wasm.peek32(pAuto))
}finally{
wasm.pstack.restore(stack);
}
})
////////////////////////////////////////////////////////////////////////
.t('selectArray/Object()', function(sqlite3){
const db = this.db;
let rc = db.selectArray('select a, b from t where a=?', 5);
T.assert(Array.isArray(rc))
.assert(2===rc.length)
.assert(5===rc[0] && 6===rc[1]);
rc = db.selectArray('select a, b from t where b=-1');
T.assert(undefined === rc);
rc = db.selectObject('select a A, b b from t where b=?', 6);
T.assert(rc && 'object'===typeof rc)
.assert(5===rc.A)
.assert(6===rc.b);
rc = db.selectArray('select a, b from t where b=-1');
T.assert(undefined === rc);
})
////////////////////////////////////////////////////////////////////////
.t('selectArrays/Objects()', function(sqlite3){
const db = this.db;
const sql = 'select a, b from t where a=? or b=? order by a';
let rc = db.selectArrays(sql, [1, 4]);
T.assert(Array.isArray(rc))
.assert(2===rc.length)
.assert(2===rc[0].length)
.assert(1===rc[0][0])
.assert(2===rc[0][1])
.assert(3===rc[1][0])
.assert(4===rc[1][1])
rc = db.selectArrays(sql, [99,99]);
T.assert(Array.isArray(rc)).assert(0===rc.length);
rc = db.selectObjects(sql, [1,4]);
T.assert(Array.isArray(rc))
.assert(2===rc.length)
.assert('object' === typeof rc[1])
.assert(1===rc[0].a)
.assert(2===rc[0].b)
.assert(3===rc[1].a)
.assert(4===rc[1].b);
})
////////////////////////////////////////////////////////////////////////
.t('selectArray/Object/Values() via INSERT/UPDATE...RETURNING', function(sqlite3){
let rc = this.db.selectObject("INSERT INTO t(a,b) VALUES(83,84) RETURNING a as AA");
T.assert(83===rc.AA);
rc = this.db.selectArray("UPDATE T set a=85 WHERE a=83 RETURNING b as BB");
T.assert(Array.isArray(rc)).assert(84===rc[0]);
//log("select * from t:",this.db.selectObjects("select * from t order by a"));
rc = this.db.selectValues("UPDATE T set a=a*1 RETURNING a");
T.assert(Array.isArray(rc))
.assert(5 === rc.length)
.assert('number'===typeof rc[0])
.assert(rc[0]|0 === rc[0] /* is small integer */);
})
////////////////////////////////////////////////////////////////////////
.t({
name: 'sqlite3_js_db_export()',
predicate: ()=>true,
test: function(sqlite3){
const db = this.db;
const xp = capi.sqlite3_js_db_export(db.pointer);
T.assert(xp instanceof Uint8Array)
.assert(xp.byteLength>0)
.assert(0 === xp.byteLength % 512);
this.dbExport = xp;
}
}/*sqlite3_js_db_export()*/)
.t({
name: 'sqlite3_js_posix_create_file()',
predicate: ()=>true,
test: function(sqlite3){
const db = this.db;
const filename = "sqlite3_js_posix_create_file.db";
capi.sqlite3_js_posix_create_file(filename, this.dbExport);
delete this.dbExport;
const db2 = new sqlite3.oo1.DB(filename,'r');
try {
const sql = "select count(*) from t";
const n = db.selectValue(sql);
T.assert(n>0 && db2.selectValue(sql) === n);
}finally{
db2.close();
wasm.sqlite3_wasm_vfs_unlink(0, filename);
}
}
}/*sqlite3_js_posix_create_file()*/)
////////////////////////////////////////////////////////////////////
.t({
name:'Scalar UDFs',
test: function(sqlite3){
const db = this.db;
db.createFunction("foo",(pCx,a,b)=>a+b);
T.assert(7===db.selectValue("select foo(3,4)")).
assert(5===db.selectValue("select foo(3,?)",2)).
assert(5===db.selectValue("select foo(?,?2)",[1,4])).
assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5}));
db.createFunction("bar", {
arity: -1,
xFunc: (pCx,...args)=>{
T.assert(db.pointer === capi.sqlite3_context_db_handle(pCx));
let rc = 0;
for(const v of args) rc += v;
return rc;
}
}).createFunction({
name: "asis",
xFunc: (pCx,arg)=>arg
});
T.assert(0===db.selectValue("select bar()")).
assert(1===db.selectValue("select bar(1)")).
assert(3===db.selectValue("select bar(1,2)")).
assert(-1===db.selectValue("select bar(1,2,-4)")).
assert('hi' === db.selectValue("select asis('hi')")).
assert('hi' === db.selectValue("select ?",'hi')).
assert(null === db.selectValue("select null")).
assert(null === db.selectValue("select asis(null)")).
assert(1 === db.selectValue("select ?",1)).
assert(2 === db.selectValue("select ?",[2])).
assert(3 === db.selectValue("select $a",{$a:3})).
assert(T.eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))).
assert(T.eqApprox(1.3,db.selectValue("select asis(1 + 0.3)")));
let blobArg = new Uint8Array([0x68, 0x69]);
let blobRc = db.selectValue(
"select asis(?1)",
blobArg.buffer/*confirm that ArrayBuffer is handled as a Uint8Array*/
);
T.assert(blobRc instanceof Uint8Array).
assert(2 === blobRc.length).
assert(0x68==blobRc[0] && 0x69==blobRc[1]);
blobRc = db.selectValue("select asis(X'6869')");
T.assert(blobRc instanceof Uint8Array).
assert(2 === blobRc.length).
assert(0x68==blobRc[0] && 0x69==blobRc[1]);
blobArg = new Int8Array([0x68, 0x69]);
//debug("blobArg=",blobArg);
blobRc = db.selectValue("select asis(?1)", blobArg);
T.assert(blobRc instanceof Uint8Array).
assert(2 === blobRc.length);
//debug("blobRc=",blobRc);
T.assert(0x68==blobRc[0] && 0x69==blobRc[1]);
let rc = sqlite3.capi.sqlite3_create_function_v2(
this.db, "foo", 0, -1, 0, 0, 0, 0, 0
);
T.assert(
sqlite3.capi.SQLITE_FORMAT === rc,
"For invalid eTextRep argument."
);
rc = sqlite3.capi.sqlite3_create_function_v2(this.db, "foo", 0);
T.assert(
sqlite3.capi.SQLITE_MISUSE === rc,
"For invalid arg count."
);
/* Confirm that we can map and unmap the same function with
multiple arities... */
const fCounts = [0,0];
const fArityCheck = function(pCx){
return ++fCounts[arguments.length-1];
};
//wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
rc = capi.sqlite3_create_function_v2(
db, "nary", 0, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0
);
T.assert( 0===rc );
rc = capi.sqlite3_create_function_v2(
db, "nary", 1, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0
);
T.assert( 0===rc );
const sqlFArity0 = "select nary()";
const sqlFArity1 = "select nary(1)";
T.assert( 1 === db.selectValue(sqlFArity0) )
.assert( 1 === fCounts[0] ).assert( 0 === fCounts[1] );
T.assert( 1 === db.selectValue(sqlFArity1) )
.assert( 1 === fCounts[0] ).assert( 1 === fCounts[1] );
capi.sqlite3_create_function_v2(
db, "nary", 0, capi.SQLITE_UTF8, 0, 0, 0, 0, 0
);
T.mustThrowMatching((()=>db.selectValue(sqlFArity0)),
(e)=>((e instanceof sqlite3.SQLite3Error)
&& e.message.indexOf("wrong number of arguments")>0),
"0-arity variant was uninstalled.");
T.assert( 2 === db.selectValue(sqlFArity1) )
.assert( 1 === fCounts[0] ).assert( 2 === fCounts[1] );
capi.sqlite3_create_function_v2(
db, "nary", 1, capi.SQLITE_UTF8, 0, 0, 0, 0, 0
);
T.mustThrowMatching((()=>db.selectValue(sqlFArity1)),
(e)=>((e instanceof sqlite3.SQLite3Error)
&& e.message.indexOf("no such function")>0),
"1-arity variant was uninstalled.");
//wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
}
})
////////////////////////////////////////////////////////////////////
.t({
name: 'Aggregate UDFs',
//predicate: ()=>false,
test: function(sqlite3){
const db = this.db;
const sjac = capi.sqlite3_js_aggregate_context;
db.createFunction({
name: 'summer',
xStep: (pCtx, n)=>{
const ac = sjac(pCtx, 4);
wasm.poke32(ac, wasm.peek32(ac) + Number(n));
},
xFinal: (pCtx)=>{
const ac = sjac(pCtx, 0);
return ac ? wasm.peek32(ac) : 0;
}
});
let v = db.selectValue([
"with cte(v) as (",
"select 3 union all select 5 union all select 7",
") select summer(v), summer(v+1) from cte"
/* ------------------^^^^^^^^^^^ ensures that we're handling
sqlite3_aggregate_context() properly. */
]);
T.assert(15===v);
T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"),
/wrong number of arguments/);
db.createFunction({
name: 'summerN',
arity: -1,
xStep: (pCtx, ...args)=>{
const ac = sjac(pCtx, 4);
let sum = wasm.peek32(ac);
for(const v of args) sum += Number(v);
wasm.poke32(ac, sum);
},
xFinal: (pCtx)=>{
const ac = sjac(pCtx, 0);
capi.sqlite3_result_int( pCtx, ac ? wasm.peek32(ac) : 0 );
// xFinal() may either return its value directly or call
// sqlite3_result_xyz() and return undefined. Both are
// functionally equivalent.
}
});
T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)'));
T.mustThrowMatching(()=>{
db.createFunction('nope',{
xFunc: ()=>{}, xStep: ()=>{}
});
}, /scalar or aggregate\?/);
T.mustThrowMatching(()=>{
db.createFunction('nope',{xStep: ()=>{}});
}, /Missing xFinal/);
T.mustThrowMatching(()=>{
db.createFunction('nope',{xFinal: ()=>{}});
}, /Missing xStep/);
T.mustThrowMatching(()=>{
db.createFunction('nope',{});
}, /Missing function-type properties/);
T.mustThrowMatching(()=>{
db.createFunction('nope',{xFunc:()=>{}, xDestroy:'nope'});
}, /xDestroy property must be a function/);
T.mustThrowMatching(()=>{
db.createFunction('nope',{xFunc:()=>{}, pApp:'nope'});
}, /Invalid value for pApp/);
}
}/*aggregate UDFs*/)
////////////////////////////////////////////////////////////////////////
.t({
name: 'Aggregate UDFs (64-bit)',
predicate: ()=>wasm.bigIntEnabled,
//predicate: ()=>false,
test: function(sqlite3){
const db = this.db;
const sjac = capi.sqlite3_js_aggregate_context;
db.createFunction({
name: 'summer64',
xStep: (pCtx, n)=>{
const ac = sjac(pCtx, 8);
wasm.poke64(ac, wasm.peek64(ac) + BigInt(n));
},
xFinal: (pCtx)=>{
const ac = sjac(pCtx, 0);
return ac ? wasm.peek64(ac) : 0n;
}
});
let v = db.selectValue([
"with cte(v) as (",
"select 9007199254740991 union all select 1 union all select 2",
") select summer64(v), summer64(v+1) from cte"
]);
T.assert(9007199254740994n===v);
}
}/*aggregate UDFs*/)
////////////////////////////////////////////////////////////////////
.t({
name: 'Window UDFs',
//predicate: ()=>false,
test: function(){
/* Example window function, table, and results taken from:
https://sqlite.org/windowfunctions.html#udfwinfunc */
const db = this.db;
const sjac = (cx,n=4)=>capi.sqlite3_js_aggregate_context(cx,n);
const xValueFinal = (pCtx)=>{
const ac = sjac(pCtx, 0);
return ac ? wasm.peek32(ac) : 0;
};
const xStepInverse = (pCtx, n)=>{
const ac = sjac(pCtx);
wasm.poke32(ac, wasm.peek32(ac) + Number(n));
};
db.createFunction({
name: 'winsumint',
xStep: (pCtx, n)=>xStepInverse(pCtx, n),
xInverse: (pCtx, n)=>xStepInverse(pCtx, -n),
xFinal: xValueFinal,
xValue: xValueFinal
});
db.exec([
"CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
"('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
]);
let rc = db.exec({
returnValue: 'resultRows',
sql:[
"SELECT x, winsumint(y) OVER (",
"ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
") AS sum_y ",
"FROM twin ORDER BY x;"
]
});
T.assert(Array.isArray(rc))
.assert(5 === rc.length);
let count = 0;
for(const row of rc){
switch(++count){
case 1: T.assert('a'===row[0] && 9===row[1]); break;
case 2: T.assert('b'===row[0] && 12===row[1]); break;
case 3: T.assert('c'===row[0] && 16===row[1]); break;
case 4: T.assert('d'===row[0] && 12===row[1]); break;
case 5: T.assert('e'===row[0] && 9===row[1]); break;
default: toss("Too many rows to window function.");
}
}
const resultRows = [];
rc = db.exec({
resultRows,
returnValue: 'resultRows',
sql:[
"SELECT x, winsumint(y) OVER (",
"ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
") AS sum_y ",
"FROM twin ORDER BY x;"
]
});
T.assert(rc === resultRows)
.assert(5 === rc.length);
rc = db.exec({
returnValue: 'saveSql',
sql: "select 1; select 2; -- empty\n; select 3"
});
T.assert(Array.isArray(rc))
.assert(3===rc.length)
.assert('select 1;' === rc[0])
.assert('select 2;' === rc[1])
.assert('-- empty\n; select 3' === rc[2]
/* Strange but true. */);
T.mustThrowMatching(()=>{
db.exec({sql:'', returnValue: 'nope'});
}, /^Invalid returnValue/);
db.exec("DROP TABLE twin");
}
}/*window UDFs*/)
////////////////////////////////////////////////////////////////////
.t("ATTACH", function(){
const db = this.db;
const resultRows = [];
db.exec({
sql:new TextEncoder('utf-8').encode([
// ^^^ testing string-vs-typedarray handling in exec()
"attach 'session' as foo;",
"create table foo.bar(a);",
"insert into foo.bar(a) values(1),(2),(3);",
"select a from foo.bar order by a;"
].join('')),
rowMode: 0,
resultRows
});
T.assert(3===resultRows.length)
.assert(2===resultRows[1]);
T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a'));
/** Demonstrate the JS-simplified form of the sqlite3_exec() callback... */
let colCount = 0, rowCount = 0;
let rc = capi.sqlite3_exec(
db, "select a, a*2 from foo.bar", function(aVals, aNames){
//console.warn("execCallback(",arguments,")");
colCount = aVals.length;
++rowCount;
T.assert(2===aVals.length)
.assert(2===aNames.length)
.assert(+(aVals[1]) === 2 * +(aVals[0]));
}, 0, 0
);
T.assert(0===rc).assert(3===rowCount).assert(2===colCount);
rc = capi.sqlite3_exec(
db.pointer, "select a from foo.bar", ()=>{
tossQuietly("Testing throwing from exec() callback.");
}, 0, 0
);
T.assert(capi.SQLITE_ABORT === rc);
/* Demonstrate how to get access to the "full" callback
signature, as opposed to the simplified JS-specific one... */
rowCount = colCount = 0;
const pCb = wasm.installFunction('i(pipp)', function(pVoid,nCols,aVals,aCols){
/* Tip: wasm.cArgvToJs() can be used to convert aVals and
aCols to arrays: const vals = wasm.cArgvToJs(nCols,
aVals); */
++rowCount;
colCount = nCols;
T.assert(2 === nCols)
.assert(wasm.isPtr(pVoid))
.assert(wasm.isPtr(aVals))
.assert(wasm.isPtr(aCols))
.assert(+wasm.cstrToJs(wasm.peekPtr(aVals + wasm.ptrSizeof))
=== 2 * +wasm.cstrToJs(wasm.peekPtr(aVals)));
return 0;
});
try {
T.assert(wasm.isPtr(pCb));
rc = capi.sqlite3_exec(
db, new TextEncoder('utf-8').encode("select a, a*2 from foo.bar"),
pCb, 0, 0
);
T.assert(0===rc)
.assert(3===rowCount)
.assert(2===colCount);
}finally{
wasm.uninstallFunction(pCb);
}
// Demonstrate that an OOM result does not propagate through sqlite3_exec()...
rc = capi.sqlite3_exec(
db, ["select a,"," a*2 from foo.bar"], (aVals, aNames)=>{
sqlite3.WasmAllocError.toss("just testing");
}, 0, 0
);
T.assert(capi.SQLITE_ABORT === rc);
db.exec("detach foo");
T.mustThrow(()=>db.exec("select * from foo.bar"),
"Because foo is no longer attached.");
})
////////////////////////////////////////////////////////////////////
.t({
name: 'C-side WASM tests',
predicate: ()=>(haveWasmCTests() || "Not compiled in."),
test: function(){
const w = wasm, db = this.db;
const stack = w.scopedAllocPush();
let ptrInt;
const origValue = 512;
try{
ptrInt = w.scopedAlloc(4);
w.poke32(ptrInt,origValue);
const cf = w.xGet('sqlite3_wasm_test_intptr');
const oldPtrInt = ptrInt;
T.assert(origValue === w.peek32(ptrInt));
const rc = cf(ptrInt);
T.assert(2*origValue === rc).
assert(rc === w.peek32(ptrInt)).
assert(oldPtrInt === ptrInt);
const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/;
const o64 = 0x010203040506/*>32-bit integer*/;
if(w.bigIntEnabled){
w.poke64(pi64, o64);
//log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64);
const v64 = ()=>w.peek64(pi64)
T.assert(v64() == o64);
//T.assert(o64 === w.peek64(pi64));
const cf64w = w.xGet('sqlite3_wasm_test_int64ptr');
cf64w(pi64);
T.assert(v64() == BigInt(2 * o64));
cf64w(pi64);
T.assert(v64() == BigInt(4 * o64));
const biTimes2 = w.xGet('sqlite3_wasm_test_int64_times2');
T.assert(BigInt(2 * o64) ===
biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError
in the call :/ */));
const pMin = w.scopedAlloc(16);
const pMax = pMin + 8;
const g64 = (p)=>w.peek64(p);
w.poke64([pMin, pMax], 0);
const minMaxI64 = [
w.xCall('sqlite3_wasm_test_int64_min'),
w.xCall('sqlite3_wasm_test_int64_max')
];
T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)).
assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER));
//log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]);
w.xCall('sqlite3_wasm_test_int64_minmax', pMin, pMax);
T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch").
assert(g64(pMax) === minMaxI64[1], "int64 mismatch");
//log("pMin",g64(pMin), "pMax",g64(pMax));
w.poke64(pMin, minMaxI64[0]);
T.assert(g64(pMin) === minMaxI64[0]).
assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))).
assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax)));
const rxRange = /too big/;
T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))},
rxRange).
mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))},
(e)=>rxRange.test(e.message));
}else{
log("No BigInt support. Skipping related tests.");
log("\"The problem\" here is that we can manipulate, at the byte level,",
"heap memory to set 64-bit values, but we can't get those values",
"back into JS because of the lack of 64-bit integer support.");
}
}finally{
const x = w.scopedAlloc(1), y = w.scopedAlloc(1), z = w.scopedAlloc(1);
//log("x=",x,"y=",y,"z=",z); // just looking at the alignment
w.scopedAllocPop(stack);
}
}
}/* jaccwabyt-specific tests */)
////////////////////////////////////////////////////////////////////////
.t({
name: 'virtual table #1: eponymous w/ manual exception handling',
predicate: ()=>!!capi.sqlite3_index_info,
test: function(sqlite3){
const VT = sqlite3.vtab;
const tmplCols = Object.assign(Object.create(null),{
A: 0, B: 1
});
/**
The vtab demonstrated here is a JS-ification of
ext/misc/templatevtab.c.
*/
const tmplMod = new sqlite3.capi.sqlite3_module();
T.assert(0===tmplMod.$xUpdate);
tmplMod.setupModule({
catchExceptions: false,
methods: {
xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){
try{
const args = wasm.cArgvToJs(argc, argv);
T.assert(args.length>=3)
.assert(args[0] === 'testvtab')
.assert(args[1] === 'main')
.assert(args[2] === 'testvtab');
//console.debug("xConnect() args =",args);
const rc = capi.sqlite3_declare_vtab(
pDb, "CREATE TABLE ignored(a,b)"
);
if(0===rc){
const t = VT.xVtab.create(ppVtab);
T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab)));
}
return rc;
}catch(e){
if(!(e instanceof sqlite3.WasmAllocError)){
wasm.dealloc(wasm.peekPtr, pzErr);
wasm.pokePtr(pzErr, wasm.allocCString(e.message));
}
return VT.xError('xConnect',e);
}
},
xCreate: true /* just for testing. Will be removed afterwards. */,
xDisconnect: function(pVtab){
try {
VT.xVtab.unget(pVtab).dispose();
return 0;
}catch(e){
return VT.xError('xDisconnect',e);
}
},
xOpen: function(pVtab, ppCursor){
try{
const t = VT.xVtab.get(pVtab),
c = VT.xCursor.create(ppCursor);
T.assert(t instanceof capi.sqlite3_vtab)
.assert(c instanceof capi.sqlite3_vtab_cursor);
c._rowId = 0;
return 0;
}catch(e){
return VT.xError('xOpen',e);
}
},
xClose: function(pCursor){
try{
const c = VT.xCursor.unget(pCursor);
T.assert(c instanceof capi.sqlite3_vtab_cursor)
.assert(!VT.xCursor.get(pCursor));
c.dispose();
return 0;
}catch(e){
return VT.xError('xClose',e);
}
},
xNext: function(pCursor){
try{
const c = VT.xCursor.get(pCursor);
++c._rowId;
return 0;
}catch(e){
return VT.xError('xNext',e);
}
},
xColumn: function(pCursor, pCtx, iCol){
try{
const c = VT.xCursor.get(pCursor);
switch(iCol){
case tmplCols.A:
capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
break;
case tmplCols.B:
capi.sqlite3_result_int(pCtx, 2000 + c._rowId);
break;
default: sqlite3.SQLite3Error.toss("Invalid column id",iCol);
}
return 0;
}catch(e){
return VT.xError('xColumn',e);
}
},
xRowid: function(pCursor, ppRowid64){
try{
const c = VT.xCursor.get(pCursor);
VT.xRowid(ppRowid64, c._rowId);
return 0;
}catch(e){
return VT.xError('xRowid',e);
}
},
xEof: function(pCursor){
const c = VT.xCursor.get(pCursor),
rc = c._rowId>=10;
return rc;
},
xFilter: function(pCursor, idxNum, idxCStr,
argc, argv/* [sqlite3_value* ...] */){
try{
const c = VT.xCursor.get(pCursor);
c._rowId = 0;
const list = capi.sqlite3_values_to_js(argc, argv);
T.assert(argc === list.length);
//log(argc,"xFilter value(s):",list);
return 0;
}catch(e){
return VT.xError('xFilter',e);
}
},
xBestIndex: function(pVtab, pIdxInfo){
try{
//const t = VT.xVtab.get(pVtab);
const sii = capi.sqlite3_index_info;
const pii = new sii(pIdxInfo);
pii.$estimatedRows = 10;
pii.$estimatedCost = 10.0;
//log("xBestIndex $nConstraint =",pii.$nConstraint);
if(pii.$nConstraint>0){
// Validate nthConstraint() and nthConstraintUsage()
const max = pii.$nConstraint;
for(let i=0; i < max; ++i ){
let v = pii.nthConstraint(i,true);
T.assert(wasm.isPtr(v));
v = pii.nthConstraint(i);
T.assert(v instanceof sii.sqlite3_index_constraint)
.assert(v.pointer >= pii.$aConstraint);
v.dispose();
v = pii.nthConstraintUsage(i,true);
T.assert(wasm.isPtr(v));
v = pii.nthConstraintUsage(i);
T.assert(v instanceof sii.sqlite3_index_constraint_usage)
.assert(v.pointer >= pii.$aConstraintUsage);
v.$argvIndex = i;//just to get some values into xFilter
v.dispose();
}
}
//log("xBestIndex $nOrderBy =",pii.$nOrderBy);
if(pii.$nOrderBy>0){
// Validate nthOrderBy()
const max = pii.$nOrderBy;
for(let i=0; i < max; ++i ){
let v = pii.nthOrderBy(i,true);
T.assert(wasm.isPtr(v));
v = pii.nthOrderBy(i);
T.assert(v instanceof sii.sqlite3_index_orderby)
.assert(v.pointer >= pii.$aOrderBy);
v.dispose();
}
}
pii.dispose();
return 0;
}catch(e){
return VT.xError('xBestIndex',e);
}
}
}
});
this.db.onclose.disposeAfter.push(tmplMod);
T.assert(0===tmplMod.$xUpdate)
.assert(tmplMod.$xCreate)
.assert(tmplMod.$xCreate === tmplMod.$xConnect,
"setup() must make these equivalent and "+
"installMethods() must avoid re-compiling identical functions");
tmplMod.$xCreate = 0 /* make tmplMod eponymous-only */;
let rc = capi.sqlite3_create_module(
this.db, "testvtab", tmplMod, 0
);
this.db.checkRc(rc);
const list = this.db.selectArrays(
"SELECT a,b FROM testvtab where a<9999 and b>1 order by a, b"
/* Query is shaped so that it will ensure that some constraints
end up in xBestIndex(). */
);
T.assert(10===list.length)
.assert(1000===list[0][0])
.assert(2009===list[list.length-1][1]);
}
})/*custom vtab #1*/
////////////////////////////////////////////////////////////////////////
.t({
name: 'virtual table #2: non-eponymous w/ automated exception wrapping',
predicate: ()=>!!capi.sqlite3_index_info,
test: function(sqlite3){
const VT = sqlite3.vtab;
const tmplCols = Object.assign(Object.create(null),{
A: 0, B: 1
});
/**
The vtab demonstrated here is a JS-ification of
ext/misc/templatevtab.c.
*/
let throwOnCreate = 1 ? 0 : capi.SQLITE_CANTOPEN
/* ^^^ just for testing exception wrapping. Note that sqlite
always translates errors from a vtable to a generic
SQLITE_ERROR unless it's from xConnect()/xCreate() and that
callback sets an error string. */;
const vtabTrace = 1
? ()=>{}
: (methodName,...args)=>console.debug('sqlite3_module::'+methodName+'():',...args);
const modConfig = {
/* catchExceptions changes how the methods are wrapped */
catchExceptions: true,
name: "vtab2test",
methods:{
xCreate: function(pDb, pAux, argc, argv, ppVtab, pzErr){
vtabTrace("xCreate",...arguments);
if(throwOnCreate){
sqlite3.SQLite3Error.toss(
throwOnCreate,
"Throwing a test exception."
);
}
const args = wasm.cArgvToJs(argc, argv);
vtabTrace("xCreate","argv:",args);
T.assert(args.length>=3);
const rc = capi.sqlite3_declare_vtab(
pDb, "CREATE TABLE ignored(a,b)"
);
if(0===rc){
const t = VT.xVtab.create(ppVtab);
T.assert(t === VT.xVtab.get(wasm.peekPtr(ppVtab)));
vtabTrace("xCreate",...arguments," ppVtab =",t.pointer);
}
return rc;
},
xConnect: true,
xDestroy: function(pVtab){
vtabTrace("xDestroy/xDisconnect",pVtab);
VT.xVtab.dispose(pVtab);
},
xDisconnect: true,
xOpen: function(pVtab, ppCursor){
const t = VT.xVtab.get(pVtab),
c = VT.xCursor.create(ppCursor);
T.assert(t instanceof capi.sqlite3_vtab)
.assert(c instanceof capi.sqlite3_vtab_cursor);
vtabTrace("xOpen",...arguments," cursor =",c.pointer);
c._rowId = 0;
},
xClose: function(pCursor){
vtabTrace("xClose",...arguments);
const c = VT.xCursor.unget(pCursor);
T.assert(c instanceof capi.sqlite3_vtab_cursor)
.assert(!VT.xCursor.get(pCursor));
c.dispose();
},
xNext: function(pCursor){
vtabTrace("xNext",...arguments);
const c = VT.xCursor.get(pCursor);
++c._rowId;
},
xColumn: function(pCursor, pCtx, iCol){
vtabTrace("xColumn",...arguments);
const c = VT.xCursor.get(pCursor);
switch(iCol){
case tmplCols.A:
capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
break;
case tmplCols.B:
capi.sqlite3_result_int(pCtx, 2000 + c._rowId);
break;
default: sqlite3.SQLite3Error.toss("Invalid column id",iCol);
}
},
xRowid: function(pCursor, ppRowid64){
vtabTrace("xRowid",...arguments);
const c = VT.xCursor.get(pCursor);
VT.xRowid(ppRowid64, c._rowId);
},
xEof: function(pCursor){
vtabTrace("xEof",...arguments);
return VT.xCursor.get(pCursor)._rowId>=10;
},
xFilter: function(pCursor, idxNum, idxCStr,
argc, argv/* [sqlite3_value* ...] */){
vtabTrace("xFilter",...arguments);
const c = VT.xCursor.get(pCursor);
c._rowId = 0;
const list = capi.sqlite3_values_to_js(argc, argv);
T.assert(argc === list.length);
},
xBestIndex: function(pVtab, pIdxInfo){
vtabTrace("xBestIndex",...arguments);
//const t = VT.xVtab.get(pVtab);
const pii = VT.xIndexInfo(pIdxInfo);
pii.$estimatedRows = 10;
pii.$estimatedCost = 10.0;
pii.dispose();
}
}/*methods*/
};
const tmplMod = VT.setupModule(modConfig);
T.assert(1===tmplMod.$iVersion);
this.db.onclose.disposeAfter.push(tmplMod);
this.db.checkRc(capi.sqlite3_create_module(
this.db.pointer, modConfig.name, tmplMod.pointer, 0
));
this.db.exec([
"create virtual table testvtab2 using ",
modConfig.name,
"(arg1 blah, arg2 bloop)"
]);
if(0){
/* If we DROP TABLE then xDestroy() is called. If the
vtab is instead destroyed when the db is closed,
xDisconnect() is called. */
this.db.onclose.disposeBefore.push(function(db){
console.debug("Explicitly dropping testvtab2 via disposeBefore handler...");
db.exec(
/** DROP TABLE is the only way to get xDestroy() to be called. */
"DROP TABLE testvtab2"
);
});
}
let list = this.db.selectArrays(
"SELECT a,b FROM testvtab2 where a<9999 and b>1 order by a, b"
/* Query is shaped so that it will ensure that some
constraints end up in xBestIndex(). */
);
T.assert(10===list.length)
.assert(1000===list[0][0])
.assert(2009===list[list.length-1][1]);
list = this.db.selectArrays(
"SELECT a,b FROM testvtab2 where a<9999 and b>1 order by b, a limit 5"
);
T.assert(5===list.length)
.assert(1000===list[0][0])
.assert(2004===list[list.length-1][1]);
// Call it as a table-valued function...
list = this.db.selectArrays([
"SELECT a,b FROM ", modConfig.name,
" where a<9999 and b>1 order by b, a limit 1"
]);
T.assert(1===list.length)
.assert(1000===list[0][0])
.assert(2000===list[0][1]);
}
})/*custom vtab #2*/
////////////////////////////////////////////////////////////////////////
.t('Custom collation', function(sqlite3){
let collationCounter = 0;
let myCmp = function(pArg,n1,p1,n2,p2){
//int (*)(void*,int,const void*,int,const void*)
++collationCounter;
const rc = wasm.exports.sqlite3_strnicmp(p1,p2,(n1<n2?n1:n2));
return rc ? rc : (n1 - n2);
};
let rc = capi.sqlite3_create_collation_v2(this.db, "mycollation", capi.SQLITE_UTF8,
0, myCmp, 0);
this.db.checkRc(rc);
rc = this.db.selectValue("select 'hi' = 'HI' collate mycollation");
T.assert(1===rc).assert(1===collationCounter);
rc = this.db.selectValue("select 'hii' = 'HI' collate mycollation");
T.assert(0===rc).assert(2===collationCounter);
rc = this.db.selectValue("select 'hi' = 'HIi' collate mycollation");
T.assert(0===rc).assert(3===collationCounter);
rc = capi.sqlite3_create_collation(this.db,"hi",capi.SQLITE_UTF8/*not enough args*/);
T.assert(capi.SQLITE_MISUSE === rc);
rc = capi.sqlite3_create_collation_v2(this.db,"hi",capi.SQLITE_UTF8+1/*invalid encoding*/,0,0,0);
T.assert(capi.SQLITE_FORMAT === rc)
.mustThrowMatching(()=>this.db.checkRc(rc),
/SQLITE_UTF8 is the only supported encoding./);
/*
We need to ensure that replacing that collation function does
the right thing. We don't have a handle to the underlying WASM
pointer from here, so cannot verify (without digging through
internal state) that the old one gets uninstalled, but we can
verify that a new one properly replaces it. (That said,
console.warn() output has shown that the uninstallation does
happen.)
*/
collationCounter = 0;
myCmp = function(pArg,n1,p1,n2,p2){
--collationCounter;
return 0;
};
rc = capi.sqlite3_create_collation_v2(this.db, "MYCOLLATION", capi.SQLITE_UTF8,
0, myCmp, 0);
this.db.checkRc(rc);
rc = this.db.selectValue("select 'hi' = 'HI' collate mycollation");
T.assert(rc>0).assert(-1===collationCounter);
rc = this.db.selectValue("select 'a' = 'b' collate mycollation");
T.assert(rc>0).assert(-2===collationCounter);
rc = capi.sqlite3_create_collation_v2(this.db, "MYCOLLATION", capi.SQLITE_UTF8,
0, null, 0);
this.db.checkRc(rc);
rc = 0;
try {
this.db.selectValue("select 'a' = 'b' collate mycollation");
}catch(e){
/* Why is e.resultCode not automatically an extended result
code? The DB() class enables those automatically. */
rc = sqlite3.capi.sqlite3_extended_errcode(this.db);
}
T.assert(capi.SQLITE_ERROR_MISSING_COLLSEQ === rc);
})/*custom collation*/
////////////////////////////////////////////////////////////////////////
.t('Close db', function(){
T.assert(this.db).assert(wasm.isPtr(this.db.pointer));
//wasm.sqlite3_wasm_db_reset(this.db); // will leak virtual tables!
this.db.close();
T.assert(!this.db.pointer);
})
;/* end of oo1 checks */
////////////////////////////////////////////////////////////////////////
T.g('kvvfs')
.t({
name: 'kvvfs is disabled in worker',
predicate: ()=>(isWorker() || "test is only valid in a Worker"),
test: function(sqlite3){
T.assert(
!capi.sqlite3_vfs_find('kvvfs'),
"Expecting kvvfs to be unregistered."
);
}
})
.t({
name: 'kvvfs in main thread',
predicate: ()=>(isUIThread()
|| "local/sessionStorage are unavailable in a Worker"),
test: function(sqlite3){
const filename = this.kvvfsDbFile = 'session';
const pVfs = capi.sqlite3_vfs_find('kvvfs');
T.assert(pVfs);
const JDb = this.JDb = sqlite3.oo1.JsStorageDb;
const unlink = this.kvvfsUnlink = ()=>{JDb.clearStorage(filename)};
unlink();
let db = new JDb(filename);
try {
db.exec([
'create table kvvfs(a);',
'insert into kvvfs(a) values(1),(2),(3)'
]);
T.assert(3 === db.selectValue('select count(*) from kvvfs'));
db.close();
db = new JDb(filename);
db.exec('insert into kvvfs(a) values(4),(5),(6)');
T.assert(6 === db.selectValue('select count(*) from kvvfs'));
}finally{
db.close();
}
}
}/*kvvfs sanity checks*/)
;/* end kvvfs tests */
////////////////////////////////////////////////////////////////////////
T.g('Hook APIs')
.t({
name: "sqlite3_commit/rollback/update_hook()",
predicate: ()=>wasm.bigIntEnabled || "Update hook requires int64",
test: function(sqlite3){
let countCommit = 0, countRollback = 0;;
const db = new sqlite3.oo1.DB(':memory:',1 ? 'c' : 'ct');
let rc = capi.sqlite3_commit_hook(db, (p)=>{
++countCommit;
return (1 === p) ? 0 : capi.SQLITE_ERROR;
}, 1);
T.assert( 0 === rc /*void pointer*/ );
// Commit hook...
T.assert( 0!=capi.sqlite3_get_autocommit(db) );
db.exec("BEGIN; SELECT 1; COMMIT");
T.assert(0 === countCommit,
"No-op transactions (mostly) do not trigger commit hook.");
db.exec("BEGIN EXCLUSIVE; SELECT 1; COMMIT");
T.assert(1 === countCommit,
"But EXCLUSIVE transactions do.");
db.transaction((d)=>{
T.assert( 0==capi.sqlite3_get_autocommit(db) );
d.exec("create table t(a)");
});
T.assert(2 === countCommit);
// Rollback hook:
rc = capi.sqlite3_rollback_hook(db, (p)=>{
++countRollback;
T.assert( 2 === p );
}, 2);
T.assert( 0 === rc /*void pointer*/ );
T.mustThrowMatching(()=>{
db.transaction('drop table t',()=>{})
}, (e)=>{
return (capi.SQLITE_MISUSE === e.resultCode)
&& ( e.message.indexOf('Invalid argument') > 0 );
});
T.assert(0 === countRollback, "Transaction was not started.");
T.mustThrowMatching(()=>{
db.transaction('immediate', ()=>{
sqlite3.SQLite3Error.toss(capi.SQLITE_FULL,'testing rollback hook');
});
}, (e)=>{
return capi.SQLITE_FULL === e.resultCode
});
T.assert(1 === countRollback);
// Update hook...
const countUpdate = Object.create(null);
capi.sqlite3_update_hook(db, (p,op,dbName,tbl,rowid)=>{
T.assert('main' === dbName.toLowerCase())
.assert('t' === tbl.toLowerCase())
.assert(3===p)
.assert('bigint' === typeof rowid);
switch(op){
case capi.SQLITE_INSERT:
case capi.SQLITE_UPDATE:
case capi.SQLITE_DELETE:
countUpdate[op] = (countUpdate[op]||0) + 1;
break;
default: toss("Unexpected hook operator:",op);
}
}, 3);
db.transaction((d)=>{
d.exec([
"insert into t(a) values(1);",
"update t set a=2;",
"update t set a=3;",
"delete from t where a=3"
// update hook is not called for an unqualified DELETE
]);
});
T.assert(1 === countRollback)
.assert(3 === countCommit)
.assert(1 === countUpdate[capi.SQLITE_INSERT])
.assert(2 === countUpdate[capi.SQLITE_UPDATE])
.assert(1 === countUpdate[capi.SQLITE_DELETE]);
//wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
T.assert(1 === capi.sqlite3_commit_hook(db, 0, 0));
T.assert(2 === capi.sqlite3_rollback_hook(db, 0, 0));
T.assert(3 === capi.sqlite3_update_hook(db, 0, 0));
//wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
db.close();
}
})/* commit/rollback/update hooks */
.t({
name: "sqlite3_preupdate_hook()",
predicate: ()=>wasm.bigIntEnabled || "Pre-update hook requires int64",
test: function(sqlite3){
const db = new sqlite3.oo1.DB(':memory:', 1 ? 'c' : 'ct');
const countHook = Object.create(null);
let rc = capi.sqlite3_preupdate_hook(
db, function(p, pDb, op, zDb, zTbl, iKey1, iKey2){
T.assert(9 === p)
.assert(db.pointer === pDb)
.assert(1 === capi.sqlite3_preupdate_count(pDb))
.assert( 0 > capi.sqlite3_preupdate_blobwrite(pDb) );
countHook[op] = (countHook[op]||0) + 1;
switch(op){
case capi.SQLITE_INSERT:
case capi.SQLITE_UPDATE:
T.assert('number' === typeof capi.sqlite3_preupdate_new_js(pDb, 0));
break;
case capi.SQLITE_DELETE:
T.assert('number' === typeof capi.sqlite3_preupdate_old_js(pDb, 0));
break;
default: toss("Unexpected hook operator:",op);
}
},
9
);
db.transaction((d)=>{
d.exec([
"create table t(a);",
"insert into t(a) values(1);",
"update t set a=2;",
"update t set a=3;",
"delete from t where a=3"
]);
});
T.assert(1 === countHook[capi.SQLITE_INSERT])
.assert(2 === countHook[capi.SQLITE_UPDATE])
.assert(1 === countHook[capi.SQLITE_DELETE]);
//wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
db.close();
//wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
}
})/*pre-update hooks*/
;/*end hook API tests*/
////////////////////////////////////////////////////////////////////////
T.g('Auto-extension API')
.t({
name: "Auto-extension sanity checks.",
test: function(sqlite3){
let counter = 0;
const fp = wasm.installFunction('i(ppp)', function(pDb,pzErr,pApi){
++counter;
return 0;
});
(new sqlite3.oo1.DB()).close();
T.assert( 0===counter );
capi.sqlite3_auto_extension(fp);
(new sqlite3.oo1.DB()).close();
T.assert( 1===counter );
(new sqlite3.oo1.DB()).close();
T.assert( 2===counter );
capi.sqlite3_cancel_auto_extension(fp);
wasm.uninstallFunction(fp);
(new sqlite3.oo1.DB()).close();
T.assert( 2===counter );
}
});
////////////////////////////////////////////////////////////////////////
T.g('Session API')
.t({
name: 'Session API sanity checks',
predicate: ()=>!!capi.sqlite3changegroup_add,
test: function(sqlite3){
warn("The session API tests could use some expansion.");
const db1 = new sqlite3.oo1.DB(), db2 = new sqlite3.oo1.DB();
const sqlInit = [
"create table t(rowid INTEGER PRIMARY KEY,a,b); ",
"insert into t(rowid,a,b) values",
"(1,'a1','b1'),",
"(2,'a2','b2'),",
"(3,'a3','b3');"
].join('');
db1.exec(sqlInit);
db2.exec(sqlInit);
T.assert(3 === db1.selectValue("select count(*) from t"))
.assert('b3' === db1.selectValue('select b from t where rowid=3'));
const stackPtr = wasm.pstack.pointer;
try{
let ppOut = wasm.pstack.allocPtr();
let rc = capi.sqlite3session_create(db1, "main", ppOut);
T.assert(0===rc);
let pSession = wasm.peekPtr(ppOut);
T.assert(pSession && wasm.isPtr(pSession));
capi.sqlite3session_table_filter(pSession, (pCtx, tbl)=>{
T.assert('t' === tbl).assert( 99 === pCtx );
return 1;
}, 99);
db1.exec([
"update t set b='bTwo' where rowid=2;",
"update t set a='aThree' where rowid=3;",
"delete from t where rowid=1;",
"insert into t(rowid,a,b) values(4,'a4','b4')"
]);
T.assert('bTwo' === db1.selectValue("select b from t where rowid=2"))
.assert(undefined === db1.selectValue('select a from t where rowid=1'))
.assert('b4' === db1.selectValue('select b from t where rowid=4'))
.assert(3 === db1.selectValue('select count(*) from t'));
const testSessionEnable = false;
if(testSessionEnable){
rc = capi.sqlite3session_enable(pSession, 0);
T.assert( 0 === rc )
.assert( 0 === capi.sqlite3session_enable(pSession, -1) );
db1.exec("delete from t where rowid=2;");
rc = capi.sqlite3session_enable(pSession, 1);
T.assert( rc > 0 )
.assert( capi.sqlite3session_enable(pSession, -1) > 0 )
.assert(undefined === db1.selectValue('select a from t where rowid=2'));
}else{
warn("sqlite3session_enable() tests are currently disabled.");
}
let db1Count = db1.selectValue("select count(*) from t");
T.assert( db1Count === (testSessionEnable ? 2 : 3) );
/* Capture changeset and destroy session. */
let pnChanges = wasm.pstack.alloc('i32'),
ppChanges = wasm.pstack.allocPtr();
rc = capi.sqlite3session_changeset(pSession, pnChanges, ppChanges);
T.assert( 0 === rc );
capi.sqlite3session_delete(pSession);
pSession = 0;
const pChanges = wasm.peekPtr(ppChanges),
nChanges = wasm.peek32(pnChanges);
T.assert( pChanges && wasm.isPtr( pChanges ) )
.assert( nChanges > 0 );
/* Revert db1 via an inverted changeset, but keep pChanges
and nChanges for application to db2. */
rc = capi.sqlite3changeset_invert( nChanges, pChanges, pnChanges, ppChanges );
T.assert( 0 === rc );
rc = capi.sqlite3changeset_apply(
db1, wasm.peek32(pnChanges), wasm.peekPtr(ppChanges), 0, (pCtx, eConflict, pIter)=>{
return 1;
}, 0
);
T.assert( 0 === rc );
wasm.dealloc( wasm.peekPtr(ppChanges) );
pnChanges = ppChanges = 0;
T.assert('b2' === db1.selectValue("select b from t where rowid=2"))
.assert('a1' === db1.selectValue('select a from t where rowid=1'))
.assert(undefined === db1.selectValue('select b from t where rowid=4'));
db1Count = db1.selectValue("select count(*) from t");
T.assert(3 === db1Count);
/* Apply pre-reverted changeset (pChanges, nChanges) to
db2... */
rc = capi.sqlite3changeset_apply(
db2, nChanges, pChanges, 0, (pCtx, eConflict, pIter)=>{
return pCtx ? 1 : 0
}, 1
);
wasm.dealloc( pChanges );
T.assert( 0 === rc )
.assert( 'b4' === db2.selectValue('select b from t where rowid=4') )
.assert( 'aThree' === db2.selectValue('select a from t where rowid=3') )
.assert( undefined === db2.selectValue('select b from t where rowid=1') );
if(testSessionEnable){
T.assert( (undefined === db2.selectValue('select b from t where rowid=2')),
"But... the session was disabled when rowid=2 was deleted?" );
log("rowids from db2.t:",db2.selectValues('select rowid from t order by rowid'));
T.assert( 3 === db2.selectValue('select count(*) from t') );
}else{
T.assert( 'bTwo' === db2.selectValue('select b from t where rowid=2') )
.assert( 3 === db2.selectValue('select count(*) from t') );
}
}finally{
wasm.pstack.restore(stackPtr);
db1.close();
db2.close();
}
}
})/*session API sanity tests*/
;/*end of session API group*/;
////////////////////////////////////////////////////////////////////////
T.g('OPFS: Origin-Private File System',
(sqlite3)=>(sqlite3.capi.sqlite3_vfs_find("opfs")
|| 'requires "opfs" VFS'))
.t({
name: 'OPFS db sanity checks',
test: async function(sqlite3){
const filename = this.opfsDbFile = '/dir/sqlite3-tester1.db';
const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs');
T.assert(pVfs);
const unlink = this.opfsUnlink =
(fn=filename)=>{wasm.sqlite3_wasm_vfs_unlink(pVfs,fn)};
unlink();
let db = new sqlite3.oo1.OpfsDb(filename);
try {
db.exec([
'create table p(a);',
'insert into p(a) values(1),(2),(3)'
]);
T.assert(3 === db.selectValue('select count(*) from p'));
db.close();
db = new sqlite3.oo1.OpfsDb(filename);
db.exec('insert into p(a) values(4),(5),(6)');
T.assert(6 === db.selectValue('select count(*) from p'));
this.opfsDbExport = capi.sqlite3_js_db_export(db);
T.assert(this.opfsDbExport instanceof Uint8Array)
.assert(this.opfsDbExport.byteLength>0
&& 0===this.opfsDbExport.byteLength % 512);
}finally{
db.close();
unlink();
}
}
}/*OPFS db sanity checks*/)
.t({
name: 'OPFS import',
test: async function(sqlite3){
let db;
try {
const exp = this.opfsDbExport;
const filename = this.opfsDbFile;
delete this.opfsDbExport;
this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, exp);
db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
T.assert(6 === db.selectValue('select count(*) from p')).
assert( this.opfsImportSize == exp.byteLength );
db.close();
this.opfsUnlink(filename);
T.assert(!(await sqlite3.opfs.entryExists(filename)));
// Try again with a function as an input source:
let cursor = 0;
const blockSize = 512, end = exp.byteLength;
const reader = async function(){
if(cursor >= exp.byteLength){
return undefined;
}
const rv = exp.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
cursor += blockSize;
return rv;
};
this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(filename, reader);
db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
T.assert(6 === db.selectValue('select count(*) from p')).
assert( this.opfsImportSize == exp.byteLength );
}finally{
if(db) db.close();
}
}
}/*OPFS export/import*/)
.t({
name: '(Internal-use) OPFS utility APIs',
test: async function(sqlite3){
const filename = this.opfsDbFile;
const pVfs = this.opfsVfs;
const unlink = this.opfsUnlink;
T.assert(filename && pVfs && !!unlink);
delete this.opfsDbFile;
delete this.opfsVfs;
delete this.opfsUnlink;
/**************************************************************
ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended
for client-side use. It is only for this project's own
internal use. Its APIs are subject to change or removal at
any time.
***************************************************************/
const opfs = sqlite3.opfs;
const fSize = this.opfsImportSize;
delete this.opfsImportSize;
let sh;
try{
T.assert(await opfs.entryExists(filename));
const [dirHandle, filenamePart] = await opfs.getDirForFilename(filename, false);
const fh = await dirHandle.getFileHandle(filenamePart);
sh = await fh.createSyncAccessHandle();
T.assert(fSize === await sh.getSize());
await sh.close();
sh = undefined;
unlink();
T.assert(!(await opfs.entryExists(filename)));
}finally{
if(sh) await sh.close();
unlink();
}
// Some sanity checks of the opfs utility functions...
const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
const aDir = testDir+'/test/dir';
T.assert(await opfs.mkdir(aDir), "mkdir failed")
.assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists")
.assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)")
.assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed")
.assert(!(await opfs.unlink(testDir+'/test/dir')),
"delete 2b should have failed (dir already deleted)")
.assert((await opfs.unlink(testDir, true)), "delete 3 failed")
.assert(!(await opfs.entryExists(testDir)),
"entryExists(",testDir,") should have failed");
}
}/*OPFS util sanity checks*/)
;/* end OPFS tests */
////////////////////////////////////////////////////////////////////////
T.g('OPFS SyncAccessHandle Pool VFS',
(sqlite3)=>(hasOpfs() || "requires OPFS APIs"))
.t({
name: 'SAH sanity checks',
test: async function(sqlite3){
T.assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
.assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) < 0)
const inst = sqlite3.installOpfsSAHPoolVfs,
catcher = (e)=>{
error("Cannot load SAH pool VFS.",
"This might not be a problem,",
"depending on the environment.");
return false;
};
let u1, u2;
// Ensure that two immediately-consecutive installations
// resolve to the same Promise instead of triggering
// a locking error.
const P1 = inst(sahPoolConfig).then(u=>u1 = u).catch(catcher),
P2 = inst(sahPoolConfig).then(u=>u2 = u).catch(catcher);
await Promise.all([P1, P2]);
if(!(await P1)) return;
T.assert(u1 === u2)
.assert(sahPoolConfig.name === u1.vfsName)
.assert(sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
.assert(u1.getCapacity() >= sahPoolConfig.initialCapacity
/* If a test fails before we get to nuke the VFS, we
can have more than the initial capacity on the next
run. */)
.assert(u1.getCapacity() + 2 === (await u2.addCapacity(2)))
.assert(2 === (await u2.reduceCapacity(2)))
.assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) >= 0);
T.assert(0 === u1.getFileCount());
const dbName = '/foo.db';
let db = new u1.OpfsSAHPoolDb(dbName);
T.assert(db instanceof sqlite3.oo1.DB)
.assert(1 === u1.getFileCount());
db.exec([
'create table t(a);',
'insert into t(a) values(1),(2),(3)'
]);
T.assert(1 === u1.getFileCount());
T.assert(3 === db.selectValue('select count(*) from t'));
db.close();
T.assert(1 === u1.getFileCount());
db = new u2.OpfsSAHPoolDb(dbName);
T.assert(1 === u1.getFileCount());
db.close();
const fileNames = u1.getFileNames();
T.assert(1 === fileNames.length)
.assert(dbName === fileNames[0])
.assert(1 === u1.getFileCount())
if(1){ // test exportFile() and importDb()
const dbytes = u1.exportFile(dbName);
T.assert(dbytes.length >= 4096);
const dbName2 = '/exported.db';
let nWrote = u1.importDb(dbName2, dbytes);
T.assert( 2 == u1.getFileCount() )
.assert( dbytes.byteLength == nWrote );
let db2 = new u1.OpfsSAHPoolDb(dbName2);
T.assert(db2 instanceof sqlite3.oo1.DB)
.assert(3 === db2.selectValue('select count(*) from t'));
db2.close();
T.assert(true === u1.unlink(dbName2))
.assert(false === u1.unlink(dbName2))
.assert(1 === u1.getFileCount())
.assert(1 === u1.getFileNames().length);
// Try again with a function as an input source:
let cursor = 0;
const blockSize = 1024, end = dbytes.byteLength;
const reader = async function(){
if(cursor >= dbytes.byteLength){
return undefined;
}
const rv = dbytes.subarray(cursor, cursor+blockSize>end ? end : cursor+blockSize);
cursor += blockSize;
return rv;
};
nWrote = await u1.importDb(dbName2, reader);
T.assert( 2 == u1.getFileCount() );
db2 = new u1.OpfsSAHPoolDb(dbName2);
T.assert(db2 instanceof sqlite3.oo1.DB)
.assert(3 === db2.selectValue('select count(*) from t'));
db2.close();
T.assert(true === u1.unlink(dbName2))
.assert(dbytes.byteLength == nWrote);
}
T.assert(true === u1.unlink(dbName))
.assert(false === u1.unlink(dbName))
.assert(0 === u1.getFileCount())
.assert(0 === u1.getFileNames().length);
// Demonstrate that two SAH pools can coexist so long as
// they have different names.
const conf2 = JSON.parse(JSON.stringify(sahPoolConfig));
conf2.name += '-test2';
const POther = await inst(conf2);
//log("Installed second SAH instance as",conf2.name);
T.assert(0 === POther.getFileCount())
.assert(true === await POther.removeVfs());
if(0){
/* Enable this block to inspect vfs's contents via the dev
console or OPFS Explorer browser extension. The
following bits will remove them. */
return;
}
T.assert(true === await u2.removeVfs())
.assert(false === await u1.removeVfs())
.assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name));
let cErr, u3;
conf2.$testThrowInInit = new Error("Testing throwing during init.");
conf2.name = sahPoolConfig.name+'-err';
const P3 = await inst(conf2).then(u=>u3 = u).catch((e)=>cErr=e);
T.assert(P3 === conf2.$testThrowInInit)
.assert(cErr === P3)
.assert(undefined === u3)
.assert(!sqlite3.capi.sqlite3_vfs_find(conf2.name));
}
}/*OPFS SAH Pool sanity checks*/)
////////////////////////////////////////////////////////////////////////
T.g('Bug Reports')
.t({
name: 'Delete via bound parameter in subquery',
test: function(sqlite3){
// Testing https://sqlite.org/forum/forumpost/40ce55bdf5
// with the exception that that post uses "external content"
// for the FTS index.
const db = new sqlite3.oo1.DB();//(':memory:','wt');
db.exec([
"create virtual table f using fts5 (path);",
"insert into f(path) values('abc'),('def'),('ghi');"
]);
const fetchEm = ()=> db.exec({
sql: "SELECT * FROM f order by path",
rowMode: 'array'
});
const dump = function(lbl){
let rc = fetchEm();
log((lbl ? (lbl+' results') : ''),rc);
};
//dump('Full fts table');
let rc = fetchEm();
T.assert(3===rc.length);
db.exec(`
delete from f where rowid in (
select rowid from f where path = :path
)`,
{bind: {":path": "def"}}
);
//dump('After deleting one entry via subquery');
rc = fetchEm();
T.assert(2===rc.length)
.assert('abcghi'===rc.join(''));
//log('rc =',rc);
db.close();
}
})
;/*end of Bug Reports group*/;
////////////////////////////////////////////////////////////////////////
log("Loading and initializing sqlite3 WASM module...");
if(0){
globalThis.sqlite3ApiConfig = {
debug: ()=>{},
log: ()=>{},
warn: ()=>{},
error: ()=>{}
}
}
//#ifnot target=es6-module
if(!globalThis.sqlite3InitModule && !isUIThread()){
/* Vanilla worker, as opposed to an ES6 module worker */
/*
If sqlite3.js is in a directory other than this script, in order
to get sqlite3.js to resolve sqlite3.wasm properly, we have to
explicitly tell it where sqlite3.js is being loaded from. We do
that by passing the `sqlite3.dir=theDirName` URL argument to
_this_ script. That URL argument will be seen by the JS/WASM
loader and it will adjust the sqlite3.wasm path accordingly. If
sqlite3.js/.wasm are in the same directory as this script then
that's not needed.
URL arguments passed as part of the filename via importScripts()
are simply lost, and such scripts see the globalThis.location of
_this_ script.
*/
let sqlite3Js = 'sqlite3.js';
const urlParams = new URL(globalThis.location.href).searchParams;
if(urlParams.has('sqlite3.dir')){
sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js;
}
importScripts(sqlite3Js);
}
//#endif
globalThis.sqlite3InitModule.__isUnderTest =
true /* disables certain API-internal cleanup so that we can
test internal APIs from here */;
globalThis.sqlite3InitModule({
print: log,
printErr: error
}).then(async function(sqlite3){
log("Done initializing WASM/JS bits. Running tests...");
sqlite3.config.warn("Installing sqlite3 bits as global S for local dev/test purposes.");
globalThis.S = sqlite3;
/*await sqlite3.installOpfsSAHPoolVfs(sahPoolConfig)
.then((u)=>log("Loaded",u.vfsName,"VFS"))
.catch(e=>{
log("Cannot install OpfsSAHPool.",e);
});*/
capi = sqlite3.capi;
wasm = sqlite3.wasm;
log("sqlite3 version:",capi.sqlite3_libversion(),
capi.sqlite3_sourceid());
if(wasm.bigIntEnabled){
log("BigInt/int64 support is enabled.");
}else{
logClass('warning',"BigInt/int64 support is disabled.");
}
if(haveWasmCTests()){
log("sqlite3_wasm_test_...() APIs are available.");
}else{
logClass('warning',"sqlite3_wasm_test_...() APIs unavailable.");
}
log("registered vfs list =",capi.sqlite3_js_vfs_list().join(', '));
TestUtil.runTests(sqlite3);
});
})(self);