-DSQLITE_TEMP_STORE=3 \
-DSQLITE_OS_KV_OPTIONAL=1 \
'-DSQLITE_DEFAULT_UNIX_VFS="unix-none"' \
- -DSQLITE_USE_URI=1
-#SQLITE_OPT += -DSQLITE_ENABLE_MEMSYS5
-# ^^^ MEMSYS5 is hypothetically useful for non-Emscripten builds but
-# requires adding more infrastructure and fixing one spot in the
-# sqlite3 internals which calls malloc() early on.
-
-# SQLITE_OMIT_LOAD_EXTENSION: if this is true, sqlite3_vfs::xDlOpen
-# and friends may be NULL.
+ -DSQLITE_USE_URI=1 \
+ -DSQLITE_WASM_ENABLE_C_TESTS
+# ^^^ most flags are set in sqlite3-wasm.c but we need them
+# made explicit here for building speedtest1.c.
ifneq (,$(filter release,$(MAKECMDGOALS)))
emcc_opt ?= -Oz -flto
$(info Development build. Use '$(MAKE) release' for a smaller release build.)
endif
-EXPORTED_FUNCTIONS.api.in := $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api \
- $(dir.jacc)/jaccwabyt_test.exports
+EXPORTED_FUNCTIONS.api.in := $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api
EXPORTED_FUNCTIONS.api: $(EXPORTED_FUNCTIONS.api.in) $(MAKEFILE)
cat $(EXPORTED_FUNCTIONS.api.in) > $@
$(sqlite3-wasm.o): emcc.cflags += $(SQLITE_OPT)
$(sqlite3-wasm.o): $(dir.top)/sqlite3.c
sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c
-jaccwabyt_test.c := $(dir.jacc)/jaccwabyt_test.c
-# ^^^ FIXME (how?): jaccwabyt_test.c is only needed for the test apps,
-# so we don't really want to include it in release builds. However, we
-# want to test the release builds with those apps, so we cannot simply
-# elide that file in release builds. That component is critical to the
-# VFS bindings so needs to be tested along with the core APIs.
########################################################################
# call-wasm-c-compile sets up build rules
# for $1.o. $1 must be the name of a C file (with extension).
$$(emcc.bin) $$(emcc_opt_full) $$(emcc.flags) $$(emcc.cflags) -c $(1) -o $$@
CLEAN_FILES += $$($(1).o)
endef
-$(foreach c,$(sqlite3-wasm.c) $(jaccwabyt_test.c),$(eval $(call call-wasm-c-compile,$(c))))
+$(foreach c,$(sqlite3-wasm.c),$(eval $(call call-wasm-c-compile,$(c))))
$(eval $(call call-make-pre-js,sqlite3))
$(sqlite3.js): $(MAKEFILE) $(sqlite3.wasm.obj) \
EXPORTED_FUNCTIONS.api \
speedtest1.cflags := \
-I. -I.. -I$(dir.top) \
-DSQLITE_SPEEDTEST1_WASM
-speedtest1.cs := $(speedtest1.c) $(sqlite3-wasm.c) $(jaccwabyt_test.c)
+speedtest1.cs := $(speedtest1.c) $(sqlite3-wasm.c)
$(speedtest1.js): emcc.cflags+=
# speedtest1 notes re. sqlite3-wasm.o vs sqlite3-wasm.c: building against
# the latter (predictably) results in a slightly faster binary, but we're
*/
capi.sqlite3_web_rc_str = (rc)=>__rcMap[rc];
/* Bind all registered C-side structs... */
+ const notThese = Object.assign(Object.create(null),{
+ // Structs NOT to register
+ WasmTestStruct: true
+ });
for(const s of wasm.ctype.structs){
- capi[s.name] = sqlite3.StructBinder(s);
+ if(!notThese[s.name]){
+ capi[s.name] = sqlite3.StructBinder(s);
+ }
}
}/*end C constant imports*/
- (optionsObject)
In the final two cases, the function must be defined as the
- 'callback' property of the options object. In the final
+ `callback` property of the options object (optionally called
+ `xFunc` to align with the C API documentation). In the final
case, the function's name must be the 'name' property.
- This can only be used to create scalar functions, not
- aggregate or window functions. UDFs cannot be removed from
- a DB handle after they're added.
+ This can currently only be used to create scalar functions, not
+ aggregate or window functions (requires only a bit of
+ refactoring to support aggregates and window functions).
+
+ UDFs cannot currently be removed from a DB handle after they're
+ added.
On success, returns this object. Throws on error.
- .deterministic = SQLITE_DETERMINISTIC
- .directOnly = SQLITE_DIRECTONLY
- .innocuous = SQLITE_INNOCUOUS
+
+ TODO: for the (optionsObject) form, accept callbacks for
+ aggregate and window functions.
+
*/
- createFunction: function f(name, callback,opt){
+ createFunction: function f(name, callback, opt){
switch(arguments.length){
case 1: /* (optionsObject) */
opt = name;
name = opt.name;
- callback = opt.callback;
+ callback = opt.xFunc || opt.callback;
break;
case 2: /* (name, callback|optionsObject) */
if(!(callback instanceof Function)){
opt = callback;
- callback = opt.callback;
+ callback = opt.xFunc || opt.callback;
}
break;
default: break;
return rc;
},
/**
- A convenience wrapper for allocChunks() which sizes each chunks
+ A convenience wrapper for allocChunks() which sizes each chunk
as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if
safePtrSize is falsy).
*/
#define SQLITE_WASM
+#ifdef SQLITE_WASM_ENABLE_C_TESTS
+/*
+** Functions blocked off by SQLITE_WASM_TESTS are intended solely for
+** use in unit/regression testing. They may be safely omitted from
+** client-side builds.
+*/
+# define SQLITE_WASM_TESTS 1
+#else
+# define SQLITE_WASM_TESTS 0
+#endif
/*
** Threading and file locking: JS is single-threaded. Each Worker
** locking, and any similar future filesystems, threading and file
** locking support are unnecessary in the wasm build.
*/
+
+/*
+** Undefine any SQLITE_... config flags which we specifically do not
+** want undefined. Please keep these alphabetized.
+*/
#undef SQLITE_OMIT_DESERIALIZE
+
+/*
+** Define any SQLITE_... config defaults we want if they aren't
+** overridden by the builder. Please keep these alphabetized.
+*/
+
+/**********************************************************************/
+/* SQLITE_DEFAULT_... */
+#ifndef SQLITE_DEFAULT_CACHE_SIZE
+/*
+** The OPFS impls benefit tremendously from an increased cache size
+** when working on large workloads, e.g. speedtest1 --size 50 or
+** higher. On smaller workloads, e.g. speedtest1 --size 25, they
+** clearly benefit from having 4mb of cache, but not as much as a
+** larger cache benefits the larger workloads. Speed differences
+** between 2x and nearly 3x have been measured with ample page cache.
+*/
+# define SQLITE_DEFAULT_CACHE_SIZE -16777216
+#endif
+#if 0 && !defined(SQLITE_DEFAULT_PAGE_SIZE)
+/* TODO: experiment with this. */
+# define SQLITE_DEFAULT_PAGE_SIZE 8192 /*4096*/
+#endif
#ifndef SQLITE_DEFAULT_UNIX_VFS
# define SQLITE_DEFAULT_UNIX_VFS "unix-none"
#endif
+
+/**********************************************************************/
+/* SQLITE_ENABLE_... */
+#ifndef SQLITE_ENABLE_BYTECODE_VTAB
+# define SQLITE_ENABLE_BYTECODE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBPAGE_VTAB
+# define SQLITE_ENABLE_DBPAGE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBSTAT_VTAB
+# define SQLITE_ENABLE_DBSTAT_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS
+# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1
+#endif
+#ifndef SQLITE_ENABLE_FTS4
+# define SQLITE_ENABLE_FTS4 1
+#endif
+#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC
+# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1
+#endif
+#ifndef SQLITE_ENABLE_RTREE
+# define SQLITE_ENABLE_RTREE 1
+#endif
+#ifndef SQLITE_ENABLE_STMTVTAB
+# define SQLITE_ENABLE_STMTVTAB 1
+#endif
+#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+#endif
+
+/**********************************************************************/
+/* SQLITE_O... */
#ifndef SQLITE_OMIT_DEPRECATED
-# define SQLITE_OMIT_DEPRECATED
+# define SQLITE_OMIT_DEPRECATED 1
#endif
#ifndef SQLITE_OMIT_LOAD_EXTENSION
-# define SQLITE_OMIT_LOAD_EXTENSION
+# define SQLITE_OMIT_LOAD_EXTENSION 1
#endif
#ifndef SQLITE_OMIT_SHARED_CACHE
-# define SQLITE_OMIT_SHARED_CACHE
+# define SQLITE_OMIT_SHARED_CACHE 1
#endif
#ifndef SQLITE_OMIT_UTF16
-# define SQLITE_OMIT_UTF16
+# define SQLITE_OMIT_UTF16 1
+#endif
+#ifndef SQLITE_OMIT_WAL
+# define SQLITE_OMIT_WAL 1
#endif
#ifndef SQLITE_OS_KV_OPTIONAL
# define SQLITE_OS_KV_OPTIONAL 1
#endif
+
+/**********************************************************************/
+/* SQLITE_T... */
#ifndef SQLITE_TEMP_STORE
# define SQLITE_TEMP_STORE 3
#endif
#ifndef SQLITE_THREADSAFE
# define SQLITE_THREADSAFE 0
#endif
-#ifndef SQLITE_OMIT_WAL
-# define SQLITE_OMIT_WAL
-#endif
-#ifndef SQLITE_DEFAULT_CACHE_SIZE
-/*
-** The OPFS impls benefit tremendously from an increased cache size
-** when working on large workloads, e.g. speedtest1 --size 50 or
-** higher. On smaller workloads, e.g. speedtest1 --size 25, they
-** clearly benefit from having 4mb of cache, but not as much as a
-** larger cache benefits the larger workloads. Speed differences
-** between 2x and nearly 3x have been measured with ample page cache.
-*/
-# define SQLITE_DEFAULT_CACHE_SIZE -16777216
-#endif
-#if 0
-/*
-** TODO: experiment with this when back on the opfs-capable machine.
-*/
-#ifndef SQLITE_DEFAULT_PAGE_SIZE
-# define SQLITE_DEFAULT_PAGE_SIZE 8192 /*4096*/
-#endif
+/**********************************************************************/
+/* SQLITE_USE_... */
+#ifndef SQLITE_USE_URI
+# define SQLITE_USE_URI 1
#endif
#include <assert.h>
#endif
/*
-** SQLITE_WASM_KEEP is identical to EMSCRIPTEN_KEEPALIVE but is not
-** Emscripten-specific. It explicitly marks functions for export into
-** the target wasm file without requiring explicit listing of those
-** functions in Emscripten's -sEXPORTED_FUNCTIONS=... list (or
-** equivalent in other build platforms). Any function with neither
+** SQLITE_WASM_KEEP is functionally identical to EMSCRIPTEN_KEEPALIVE
+** but is not Emscripten-specific. It explicitly marks functions for
+** export into the target wasm file without requiring explicit listing
+** of those functions in Emscripten's -sEXPORTED_FUNCTIONS=... list
+** (or equivalent in other build platforms). Any function with neither
** this attribute nor which is listed as an explicit export will not
** be exported from the wasm file (but may still be used internally
** within the wasm file).
return err_code;
}
+#if SQLITE_WASM_TESTS
+struct WasmTestStruct {
+ int v4;
+ void * ppV;
+ const char * cstr;
+ int64_t v8;
+ void (*xFunc)(void*);
+};
+typedef struct WasmTestStruct WasmTestStruct;
+SQLITE_WASM_KEEP
+void sqlite3_wasm_test_struct(WasmTestStruct * s){
+ if(s){
+ s->v4 *= 2;
+ s->v8 = s->v4 * 2;
+ s->ppV = s;
+ s->cstr = __FILE__;
+ if(s->xFunc) s->xFunc(s);
+ }
+ return;
+}
+#endif /* SQLITE_WASM_TESTS */
+
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings. Unlike the
M(xDelete,"i(ss)");
M(nKeySize,"i");
} _StructBinder;
+#undef CurrentStruct
+
+#if SQLITE_WASM_TESTS
+#define CurrentStruct WasmTestStruct
+ StructBinder {
+ M(v4,"i");
+ M(cstr,"s");
+ M(ppV,"p");
+ M(v8,"j");
+ M(xFunc,"v(p)");
+ } _StructBinder;
+#undef CurrentStruct
+#endif
} out( "]"/*structs*/);
}
#endif /* __EMSCRIPTEN__ && SQLITE_WASM_WASMFS */
+#if SQLITE_WASM_TESTS
+
+SQLITE_WASM_KEEP
+int sqlite3_wasm_test_intptr(int * p){
+ return *p = *p * 2;
+}
+
+SQLITE_WASM_KEEP
+int64_t sqlite3_wasm_test_int64_max(void){
+ return (int64_t)0x7fffffffffffffff;
+}
+
+SQLITE_WASM_KEEP
+int64_t sqlite3_wasm_test_int64_min(void){
+ return ~sqlite3_wasm_test_int64_max();
+}
+
+SQLITE_WASM_KEEP
+int64_t sqlite3_wasm_test_int64_times2(int64_t x){
+ return x * 2;
+}
+
+SQLITE_WASM_KEEP
+void sqlite3_wasm_test_int64_minmax(int64_t * min, int64_t *max){
+ *max = sqlite3_wasm_test_int64_max();
+ *min = sqlite3_wasm_test_int64_min();
+ /*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/
+}
+
+SQLITE_WASM_KEEP
+int64_t sqlite3_wasm_test_int64ptr(int64_t * p){
+ /*printf("sqlite3_wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
+ return *p = *p * 2;
+}
+
+SQLITE_WASM_KEEP
+void sqlite3_wasm_test_stack_overflow(int recurse){
+ if(recurse) sqlite3_wasm_test_stack_overflow(recurse);
+}
+
+/* For testing the 'string-free' whwasmutil.xWrap() conversion. */
+SQLITE_WASM_KEEP
+char * sqlite3_wasm_test_str_hello(int fail){
+ char * s = fail ? 0 : (char *)malloc(6);
+ if(s){
+ memcpy(s, "hello", 5);
+ s[5] = 0;
+ }
+ return s;
+}
+#endif /* SQLITE_WASM_TESTS */
+
#undef SQLITE_WASM_KEEP
display: flex;
flex-direction: column;
flex-wrap: wrap;
- white-space: break-spaces;
}
textarea {
font-family: monospace;
Would that be too much magic concentrated in one place, ready to
backfire?
*/
- xcv.arg.string = xcv.arg['pointer'] = xcv.arg['*'] = function(v){
- if('string'===typeof v) return target.scopedAllocCString(v);
- return v ? xcv.arg[ptrIR](v) : null;
- };
- xcv.result.string = (i)=>target.cstringToJs(i);
- xcv.result['string:free'] = (i)=>{
+ xcv.arg.string = xcv.arg.utf8 = xcv.arg['pointer'] = xcv.arg['*']
+ = function(v){
+ if('string'===typeof v) return target.scopedAllocCString(v);
+ return v ? xcv.arg[ptrIR](v) : null;
+ };
+ xcv.result.string = xcv.result.utf8 = (i)=>target.cstringToJs(i);
+ xcv.result['string:free'] = xcv.result['utf8:free'] = (i)=>{
try { return i ? target.cstringToJs(i) : null }
finally{ target.dealloc(i) }
};
Non-numeric conversions include:
- - `string` (args): has two different semantics in order to
- accommodate various uses of certain C APIs (e.g. output-style
- strings)...
+ - `string` or `utf8` (args): has two different semantics in order
+ to accommodate various uses of certain C APIs
+ (e.g. output-style strings)...
- - If the arg is a string, it creates a _temporary_ C-string to
- pass to the exported function, cleaning it up before the
- wrapper returns. If a long-lived C-string pointer is
- required, that requires client-side code to create the
- string, then pass its pointer to the function.
+ - If the arg is a string, it creates a _temporary_
+ UTF-8-encoded C-string to pass to the exported function,
+ cleaning it up before the wrapper returns. If a long-lived
+ C-string pointer is required, that requires client-side code
+ to create the string, then pass its pointer to the function.
- Else the arg is assumed to be a pointer to a string the
client has already allocated and it's passed on as
a WASM pointer.
- - `string` (results): treats the result value as a const C-string,
- copies it to a JS string, and returns that JS string.
-
- - `string:free` (results): treats the result value as a non-const
- C-string, ownership of which has just been transfered to the
- caller. It copies the C-string to a JS string, frees the
- C-string, and returns the JS string. If such a result value is
- NULL, the JS result is `null`. Achtung: when using an API which
- returns results from a specific allocator, e.g. `my_malloc()`,
- this conversion _is not legal_. Instead, an equivalent conversion
- which uses the appropriate deallocator is required. For example:
+ - `string` or `utf8` (results): treats the result value as a
+ const C-string, encoded as UTF-8, copies it to a JS string,
+ and returns that JS string.
+
+ - `string:free` or `utf8:free) (results): treats the result value
+ as a non-const UTF-8 C-string, ownership of which has just been
+ transfered to the caller. It copies the C-string to a JS
+ string, frees the C-string, and returns the JS string. If such
+ a result value is NULL, the JS result is `null`. Achtung: when
+ using an API which returns results from a specific allocator,
+ e.g. `my_malloc()`, this conversion _is not legal_. Instead, an
+ equivalent conversion which uses the appropriate deallocator is
+ required. For example:
```js
target.xWrap.resultAdaptor('string:my_free',(i)=>{
</div>
<div>The tests and demos...
<ul id='test-list'>
+ <li>Core-most tests
+ <ul>
+ <li><a href='tester1.html'>tester1</a>: Core unit and
+ regression tests for the various APIs and surrounding
+ utility code.</li>
+ <li><a href='tester1.html'>tester1-worker</a>: same thing
+ but running in a Worker.</li>
+ </ul>
+ </li>
<li>High-level apps and demos...
<ul>
<li><a href='fiddle/fiddle.html'>fiddle</a> is an HTML front-end
</li>
<li>The obligatory "misc." category...
<ul>
- <li><a href='testing1.html'>testing1</a>: sanity tests of the core APIs and surrounding utility code.</li>
<li><a href='testing2.html'>testing2</a>: Worker-based test of OO API #1.</li>
<li><a href='testing-worker1-promiser.html'>testing-worker1-promiser</a>:
tests for the Promise-based wrapper of the Worker-based API.</li>
+++ /dev/null
-#include <assert.h>
-#include <string.h> /* memset() */
-#include <stddef.h> /* offsetof() */
-#include <stdio.h> /* snprintf() */
-#include <stdint.h> /* int64_t */
-/*#include <stdlib.h>*/ /* malloc/free(), needed for emscripten exports. */
-extern void * malloc(size_t);
-extern void free(void *);
-
-/*
-** 2022-06-25
-**
-** 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.
-**
-***********************************************************************
-**
-** Utility functions for use with the emscripten/WASM bits. These
-** functions ARE NOT part of the sqlite3 public API. They are strictly
-** for internal use by the JS/WASM bindings.
-**
-** This file is intended to be WASM-compiled together with sqlite3.c,
-** e.g.:
-**
-** emcc ... sqlite3.c wasm_util.c
-*/
-
-/*
-** Experimenting with output parameters.
-*/
-int jaccwabyt_test_intptr(int * p){
- if(1==((int)p)%3){
- /* kludge to get emscripten to export malloc() and free() */;
- free(malloc(0));
- }
- return *p = *p * 2;
-}
-int64_t jaccwabyt_test_int64_max(void){
- return (int64_t)0x7fffffffffffffff;
-}
-int64_t jaccwabyt_test_int64_min(void){
- return ~jaccwabyt_test_int64_max();
-}
-int64_t jaccwabyt_test_int64_times2(int64_t x){
- return x * 2;
-}
-
-void jaccwabyt_test_int64_minmax(int64_t * min, int64_t *max){
- *max = jaccwabyt_test_int64_max();
- *min = jaccwabyt_test_int64_min();
- /*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/
-}
-int64_t jaccwabyt_test_int64ptr(int64_t * p){
- /*printf("jaccwabyt_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
- return *p = *p * 2;
-}
-
-void jaccwabyt_test_stack_overflow(int recurse){
- if(recurse) jaccwabyt_test_stack_overflow(recurse);
-}
-
-struct WasmTestStruct {
- int v4;
- void * ppV;
- const char * cstr;
- int64_t v8;
- void (*xFunc)(void*);
-};
-typedef struct WasmTestStruct WasmTestStruct;
-void jaccwabyt_test_struct(WasmTestStruct * s){
- if(s){
- s->v4 *= 2;
- s->v8 = s->v4 * 2;
- s->ppV = s;
- s->cstr = __FILE__;
- if(s->xFunc) s->xFunc(s);
- }
- return;
-}
-
-/** For testing the 'string-free' whwasmutil.xWrap() conversion. */
-char * jaccwabyt_test_str_hello(int fail){
- char * s = fail ? 0 : (char *)malloc(6);
- if(s){
- memcpy(s, "hello", 5);
- s[5] = 0;
- }
- return s;
-}
-
-/*
-** Returns a NUL-terminated string containing a JSON-format metadata
-** regarding C structs, for use with the StructBinder API. The
-** returned memory is static and is only written to the first time
-** this is called.
-*/
-const char * jaccwabyt_test_ctype_json(void){
- static char strBuf[1024 * 8] = {0};
- int n = 0, structCount = 0, groupCount = 0;
- char * pos = &strBuf[1] /* skip first byte for now to help protect
- against a small race condition */;
- char const * const zEnd = pos + sizeof(strBuf);
- if(strBuf[0]) return strBuf;
- /* Leave first strBuf[0] at 0 until the end to help guard against a
- tiny race condition. If this is called twice concurrently, they
- might end up both writing to strBuf, but they'll both write the
- same thing, so that's okay. If we set byte 0 up front then the
- 2nd instance might return a partially-populated string. */
-
- ////////////////////////////////////////////////////////////////////
- // First we need to build up our macro framework...
- ////////////////////////////////////////////////////////////////////
- // Core output macros...
-#define lenCheck assert(pos < zEnd - 100)
-#define outf(format,...) \
- pos += snprintf(pos, ((size_t)(zEnd - pos)), format, __VA_ARGS__); \
- lenCheck
-#define out(TXT) outf("%s",TXT)
-#define CloseBrace(LEVEL) \
- assert(LEVEL<5); memset(pos, '}', LEVEL); pos+=LEVEL; lenCheck
-
- ////////////////////////////////////////////////////////////////////
- // Macros for emitting StructBinder descriptions...
-#define StructBinder__(TYPE) \
- n = 0; \
- outf("%s{", (structCount++ ? ", " : "")); \
- out("\"name\": \"" # TYPE "\","); \
- outf("\"sizeof\": %d", (int)sizeof(TYPE)); \
- out(",\"members\": {");
-#define StructBinder_(T) StructBinder__(T)
-// ^^^ indirection needed to expand CurrentStruct
-#define StructBinder StructBinder_(CurrentStruct)
-#define _StructBinder CloseBrace(2)
-#define M(MEMBER,SIG) \
- outf("%s\"%s\": " \
- "{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \
- (n++ ? ", " : ""), #MEMBER, \
- (int)offsetof(CurrentStruct,MEMBER), \
- (int)sizeof(((CurrentStruct*)0)->MEMBER), \
- SIG)
- // End of macros
- ////////////////////////////////////////////////////////////////////
-
- out("\"structs\": ["); {
-
-#define CurrentStruct WasmTestStruct
- StructBinder {
- M(v4,"i");
- M(cstr,"s");
- M(ppV,"p");
- M(v8,"j");
- M(xFunc,"v(p)");
- } _StructBinder;
-#undef CurrentStruct
-
- } out( "]"/*structs*/);
- out("}"/*top-level object*/);
- *pos = 0;
- strBuf[0] = '{'/*end of the race-condition workaround*/;
- return strBuf;
-#undef DefGroup
-#undef Def
-#undef _DefGroup
-#undef StructBinder
-#undef StructBinder_
-#undef StructBinder__
-#undef M
-#undef _StructBinder
-#undef CurrentStruct
-#undef CloseBrace
-#undef out
-#undef outf
-#undef lenCheck
-}
+++ /dev/null
-_jaccwabyt_test_intptr
-_jaccwabyt_test_int64ptr
-_jaccwabyt_test_int64_max
-_jaccwabyt_test_int64_min
-_jaccwabyt_test_int64_minmax
-_jaccwabyt_test_int64_times2
-_jaccwabyt_test_struct
-_jaccwabyt_test_ctype_json
-_jaccwabyt_test_stack_overflow
-_jaccwabyt_test_str_hello
const isWorker = ()=>!isUIThread();
/* Predicate for tests/groups. */
const testIsTodo = ()=>false;
- const haveJaccwabytTests = function(){
- return !!wasm.exports.jaccwabyt_test_int64_max;
+ const haveWasmCTests = function(){
+ return !!wasm.exports.sqlite3_wasm_test_int64_max;
};
const mapToString = (v)=>{
switch(typeof v){
////////////////////////////////////////////////////////////////////////
// End of infrastructure setup. Now define the tests...
////////////////////////////////////////////////////////////////////////
-
+
+ ////////////////////////////////////////////////////////////////////
T.g('Basic sanity checks')
.t('Namespace object checks', function(sqlite3){
const wasmCtypes = wasm.ctype;
.assert(e instanceof sqlite3.WasmAllocError);
}
})
+ ////////////////////////////////////////////////////////////////////
.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.capi.wasm', function(sqlite3){
+ 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);
+ }
+ }
+
+ //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.cstringToJs(cpy)).
+ assert(chr('o') === w.getMemValue(cpy+n-1)).
+ assert(0 === w.getMemValue(cpy+n));
+ let cStr2 = w.scopedAllocCString("HI!!!");
+ rc = w.cstrncpy(cpy, cStr2, 3);
+ T.assert(3===rc).
+ assert("HI!lo" === w.cstringToJs(cpy)).
+ assert(chr('!') === w.getMemValue(cpy+2)).
+ assert(chr('l') === w.getMemValue(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 cstr = w.allocCString("hällo, world");
+ const n = w.cstrlen(cstr);
+ T.assert(13 === n)
+ .assert(0===w.getMemValue(cstr+n))
+ .assert(chr('d')===w.getMemValue(cstr+n-1));
+ }
+
+ //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.getMemValue(z1,'i32'), 'allocPtr() must zero the targets')
+ .assert(0===w.getMemValue(z3,'i32'));
+ }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.getMemValue(cstr+n))
+ .assert(chr('d')===w.getMemValue(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);
+ if(haveWasmCTests()){
+ fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:free',['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(){
+ let pI1 = w.scopedAlloc(8), pI2 = pI1+4;
+ w.setMemValue(pI1, 0,'*')(pI2, 0, '*');
+ let f = w.xWrap('sqlite3_wasm_test_int64_minmax',undefined,['i64*','i64*']);
+ let r1 = w.getMemValue(pI1, 'i64'), r2 = w.getMemValue(pI2, 'i64');
+ T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2));
+ });
+ }
+ }
+ }
+ }/*WhWasmUtil*/)
+
+ ////////////////////////////////////////////////////////////////////
+ .t('sqlite3.StructBinder (jaccwabyt)', function(sqlite3){
+ const S = sqlite3, W = S.capi.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)).
+ assert(K.resolveToInstance(k1.pointer)===k1).
+ mustThrowMatching(()=>K.resolveToInstance(null,true), /is-not-a my_struct/).
+ assert(k1 === StructType.instanceForPointer(k1.pointer)).
+ 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/).
+ assert(K.instanceForPointer(k1.pointer) === k1);
+ 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);
+ 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).
+ assert(undefined === K.instanceForPointer(ptr)).
+ mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
+ const k3 = new K();
+ ptr = k3.pointer;
+ T.assert(k3 === K.instanceForPointer(ptr));
+ K.disposeAll();
+ T.assert(ptr).
+ assert(undefined === k2.pointer).
+ assert(undefined === k3.pointer).
+ assert(undefined === K.instanceForPointer(ptr));
+ }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 === StructType.instanceForPointer(wts.pointer));
+ T.assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
+ assert(0===wts.$ppV).assert(0===wts.$xFunc).
+ assert(WTStruct.instanceForPointer(wts.pointer) === wts);
+ 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;
+ T.assert(WTStruct.instanceForPointer(arg) === wts);
+ 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(autoResolvePtr ? (wts.$ppV === wts) : (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(autoResolvePtr ? (wts.$ppV === wts) : (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;
+ if(autoResolvePtr){
+ wts.$ppV = 0;
+ T.assert(!wts.$ppV);
+ WTStruct.debugFlags(0x03);
+ wts.$ppV = wts;
+ T.assert(wts === wts.$ppV)
+ WTStruct.debugFlags(0);
+ }
+ 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).
+ assert(undefined === WTStruct.instanceForPointer(ptr))
+ }finally{
+ wts.dispose();
+ }
+ }/*StructBinder*/)
+
+ ////////////////////////////////////////////////////////////////////
+ .t('sqlite3.StructBinder part 2', function(sqlite3){
+ // https://www.sqlite.org/c3ref/vfs.html
+ // https://www.sqlite.org/c3ref/io_methods.html
+ const W = wasm;
+ const sqlite3_io_methods = capi.sqlite3_io_methods,
+ sqlite3_vfs = capi.sqlite3_vfs,
+ sqlite3_file = capi.sqlite3_file;
+ //log("struct sqlite3_file", sqlite3_file.memberKeys());
+ //log("struct sqlite3_vfs", sqlite3_vfs.memberKeys());
+ //log("struct sqlite3_io_methods", sqlite3_io_methods.memberKeys());
+ const installMethod = function callee(tgt, name, func){
+ if(1===arguments.length){
+ return (n,f)=>callee(tgt,n,f);
+ }
+ if(!callee.argcProxy){
+ callee.argcProxy = function(func,sig){
+ return function(...args){
+ if(func.length!==arguments.length){
+ toss("Argument mismatch. Native signature is:",sig);
+ }
+ return func.apply(this, args);
+ }
+ };
+ callee.ondisposeRemoveFunc = function(){
+ if(this.__ondispose){
+ const who = this;
+ this.__ondispose.forEach(
+ (v)=>{
+ if('number'===typeof v){
+ try{capi.wasm.uninstallFunction(v)}
+ catch(e){/*ignore*/}
+ }else{/*wasm function wrapper property*/
+ delete who[v];
+ }
+ }
+ );
+ delete this.__ondispose;
+ }
+ };
+ }/*static init*/
+ const sigN = tgt.memberSignature(name),
+ memKey = tgt.memberKey(name);
+ //log("installMethod",tgt, name, sigN);
+ if(!tgt.__ondispose){
+ T.assert(undefined === tgt.ondispose);
+ tgt.ondispose = [callee.ondisposeRemoveFunc];
+ tgt.__ondispose = [];
+ }
+ const fProxy = callee.argcProxy(func, sigN);
+ const pFunc = capi.wasm.installFunction(fProxy, tgt.memberSignature(name, true));
+ tgt[memKey] = pFunc;
+ /**
+ ACHTUNG: function pointer IDs are from a different pool than
+ allocation IDs, starting at 1 and incrementing in steps of 1,
+ so if we set tgt[memKey] to those values, we'd very likely
+ later misinterpret them as plain old pointer addresses unless
+ unless we use some silly heuristic like "all values <5k are
+ presumably function pointers," or actually perform a function
+ lookup on every pointer to first see if it's a function. That
+ would likely work just fine, but would be kludgy.
+
+ It turns out that "all values less than X are functions" is
+ essentially how it works in wasm: a function pointer is
+ reported to the client as its index into the
+ __indirect_function_table.
+ So... once jaccwabyt can be told how to access the
+ function table, it could consider all pointer values less
+ than that table's size to be functions. As "real" pointer
+ values start much, much higher than the function table size,
+ that would likely work reasonably well. e.g. the object
+ pointer address for sqlite3's default VFS is (in this local
+ setup) 65104, whereas the function table has fewer than 600
+ entries.
+ */
+ const wrapperKey = '$'+memKey;
+ tgt[wrapperKey] = fProxy;
+ tgt.__ondispose.push(pFunc, wrapperKey);
+ //log("tgt.__ondispose =",tgt.__ondispose);
+ return (n,f)=>callee(tgt, n, f);
+ }/*installMethod*/;
+
+ const installIOMethods = function instm(iom){
+ (iom instanceof capi.sqlite3_io_methods) || toss("Invalid argument type.");
+ if(!instm._requireFileArg){
+ instm._requireFileArg = function(arg,methodName){
+ arg = capi.sqlite3_file.resolveToInstance(arg);
+ if(!arg){
+ err("sqlite3_io_methods::xClose() was passed a non-sqlite3_file.");
+ }
+ return arg;
+ };
+ instm._methods = {
+ // https://sqlite.org/c3ref/io_methods.html
+ xClose: /*i(P)*/function(f){
+ /* int (*xClose)(sqlite3_file*) */
+ log("xClose(",f,")");
+ if(!(f = instm._requireFileArg(f,'xClose'))) return capi.SQLITE_MISUSE;
+ f.dispose(/*noting that f has externally-owned memory*/);
+ return 0;
+ },
+ xRead: /*i(Ppij)*/function(f,dest,n,offset){
+ /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
+ log("xRead(",arguments,")");
+ if(!(f = instm._requireFileArg(f))) return capi.SQLITE_MISUSE;
+ capi.wasm.heap8().fill(0, dest + offset, n);
+ return 0;
+ },
+ xWrite: /*i(Ppij)*/function(f,dest,n,offset){
+ /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
+ log("xWrite(",arguments,")");
+ if(!(f=instm._requireFileArg(f,'xWrite'))) return capi.SQLITE_MISUSE;
+ return 0;
+ },
+ xTruncate: /*i(Pj)*/function(f){
+ /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */
+ log("xTruncate(",arguments,")");
+ if(!(f=instm._requireFileArg(f,'xTruncate'))) return capi.SQLITE_MISUSE;
+ return 0;
+ },
+ xSync: /*i(Pi)*/function(f){
+ /* int (*xSync)(sqlite3_file*, int flags) */
+ log("xSync(",arguments,")");
+ if(!(f=instm._requireFileArg(f,'xSync'))) return capi.SQLITE_MISUSE;
+ return 0;
+ },
+ xFileSize: /*i(Pp)*/function(f,pSz){
+ /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */
+ log("xFileSize(",arguments,")");
+ if(!(f=instm._requireFileArg(f,'xFileSize'))) return capi.SQLITE_MISUSE;
+ capi.wasm.setMemValue(pSz, 0/*file size*/);
+ return 0;
+ },
+ xLock: /*i(Pi)*/function(f){
+ /* int (*xLock)(sqlite3_file*, int) */
+ log("xLock(",arguments,")");
+ if(!(f=instm._requireFileArg(f,'xLock'))) return capi.SQLITE_MISUSE;
+ return 0;
+ },
+ xUnlock: /*i(Pi)*/function(f){
+ /* int (*xUnlock)(sqlite3_file*, int) */
+ log("xUnlock(",arguments,")");
+ if(!(f=instm._requireFileArg(f,'xUnlock'))) return capi.SQLITE_MISUSE;
+ return 0;
+ },
+ xCheckReservedLock: /*i(Pp)*/function(){
+ /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
+ log("xCheckReservedLock(",arguments,")");
+ return 0;
+ },
+ xFileControl: /*i(Pip)*/function(){
+ /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
+ log("xFileControl(",arguments,")");
+ return capi.SQLITE_NOTFOUND;
+ },
+ xSectorSize: /*i(P)*/function(){
+ /* int (*xSectorSize)(sqlite3_file*) */
+ log("xSectorSize(",arguments,")");
+ return 0/*???*/;
+ },
+ xDeviceCharacteristics:/*i(P)*/function(){
+ /* int (*xDeviceCharacteristics)(sqlite3_file*) */
+ log("xDeviceCharacteristics(",arguments,")");
+ return 0;
+ }
+ };
+ }/*static init*/
+ iom.$iVersion = 1;
+ Object.keys(instm._methods).forEach(
+ (k)=>installMethod(iom, k, instm._methods[k])
+ );
+ }/*installIOMethods()*/;
+
+ const iom = new sqlite3_io_methods, sfile = new sqlite3_file;
+ const err = console.error.bind(console);
+ try {
+ const IOM = sqlite3_io_methods, S3F = sqlite3_file;
+ //log("iom proto",iom,iom.constructor.prototype);
+ //log("sfile",sfile,sfile.constructor.prototype);
+ T.assert(0===sfile.$pMethods).assert(iom.pointer > 0);
+ //log("iom",iom);
+ sfile.$pMethods = iom.pointer;
+ T.assert(iom.pointer === sfile.$pMethods)
+ .assert(IOM.resolveToInstance(iom))
+ .assert(undefined ===IOM.resolveToInstance(sfile))
+ .mustThrow(()=>IOM.resolveToInstance(0,true))
+ .assert(S3F.resolveToInstance(sfile.pointer))
+ .assert(undefined===S3F.resolveToInstance(iom))
+ .assert(iom===IOM.resolveToInstance(sfile.$pMethods));
+ T.assert(0===iom.$iVersion);
+ installIOMethods(iom);
+ T.assert(1===iom.$iVersion);
+ //log("iom.__ondispose",iom.__ondispose);
+ T.assert(Array.isArray(iom.__ondispose)).assert(iom.__ondispose.length>10);
+ }finally{
+ iom.dispose();
+ T.assert(undefined === iom.__ondispose);
+ }
+
+ const dVfs = new sqlite3_vfs(capi.sqlite3_vfs_find(null));
+ try {
+ const SB = sqlite3.StructBinder;
+ T.assert(dVfs instanceof SB.StructType)
+ .assert(dVfs.pointer)
+ .assert('sqlite3_vfs' === dVfs.structName)
+ .assert(!!dVfs.structInfo)
+ .assert(SB.StructType.hasExternalPointer(dVfs))
+ .assert(dVfs.$iVersion>0)
+ .assert('number'===typeof dVfs.$zName)
+ .assert('number'===typeof dVfs.$xSleep)
+ .assert(capi.wasm.functionEntry(dVfs.$xOpen))
+ .assert(dVfs.memberIsString('zName'))
+ .assert(dVfs.memberIsString('$zName'))
+ .assert(!dVfs.memberIsString('pAppData'))
+ .mustThrowMatching(()=>dVfs.memberToJsString('xSleep'),
+ /Invalid member type signature for C-string/)
+ .mustThrowMatching(()=>dVfs.memberSignature('nope'), /nope is not a mapped/)
+ .assert('string' === typeof dVfs.memberToJsString('zName'))
+ .assert(dVfs.memberToJsString('zName')===dVfs.memberToJsString('$zName'))
+ ;
+ //log("Default VFS: @",dVfs.pointer);
+ Object.keys(sqlite3_vfs.structInfo.members).forEach(function(mname){
+ const mk = sqlite3_vfs.memberKey(mname), mbr = sqlite3_vfs.structInfo.members[mname],
+ addr = dVfs[mk], prefix = 'defaultVfs.'+mname;
+ if(1===mbr.signature.length){
+ let sep = '?', val = undefined;
+ switch(mbr.signature[0]){
+ // TODO: move this into an accessor, e.g. getPreferredValue(member)
+ case 'i': case 'j': case 'f': case 'd': sep = '='; val = dVfs[mk]; break
+ case 'p': case 'P': sep = '@'; val = dVfs[mk]; break;
+ case 's': sep = '=';
+ val = dVfs.memberToJsString(mname);
+ break;
+ }
+ //log(prefix, sep, val);
+ }else{
+ //log(prefix," = funcptr @",addr, capi.wasm.functionEntry(addr));
+ }
+ });
+ }finally{
+ dVfs.dispose();
+ T.assert(undefined===dVfs.pointer);
+ }
+ }/*StructBinder part 2*/)
+
+ ////////////////////////////////////////////////////////////////////
.t('sqlite3.capi.wasm.pstack', function(sqlite3){
const w = sqlite3.capi.wasm, P = w.pstack;
const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError;
P.restore(stack);
}
}/*pstack tests*/)
- ;/*end of basic sanity checks*/
+
+ ////////////////////////////////////////////////////////////////////
+ ;/*end of C/WASM utils checks*/
////////////////////////////////////////////////////////////////////////
T.g('sqlite3.oo1 sanity checks')
T.assert(capi.SQLITE_MISUSE === rc)
.assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL"));
})
+
+ ////////////////////////////////////////////////////////////////////
.t('DB.Stmt sanity checks', function(S){
let pId;
let st = this.db.prepare(
T.assert(!st.pointer)
.assert(0===this.db.openStatementCount());
})
+
+ ////////////////////////////////////////////////////////////////////
.t('Table t', function(sqlite3){
const db = this.db;
let list = [];
db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)).
assert(Number.MAX_SAFE_INTEGER ===
db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER));
- if(capi.wasm.bigIntEnabled && haveJaccwabytTests()){
- const mI = capi.wasm.xCall('jaccwabyt_test_int64_max');
+ if(capi.wasm.bigIntEnabled && haveWasmCTests()){
+ const mI = capi.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)).
}
})
+ ////////////////////////////////////////////////////////////////////
.t('Scalar UDFs', function(sqlite3){
const db = this.db;
db.createFunction("foo",(pCx,a,b)=>a+b);
T.assert(0x68==blobRc[0] && 0x69==blobRc[1]);
})
+ ////////////////////////////////////////////////////////////////////
.t({
name: 'Aggregate UDFs (tests are TODO)',
predicate: testIsTodo
})
+ ////////////////////////////////////////////////////////////////////
.t({
name: 'Window UDFs (tests are TODO)',
predicate: testIsTodo
})
+ ////////////////////////////////////////////////////////////////////
.t("ATTACH", function(){
const db = this.db;
const resultRows = [];
T.mustThrow(()=>db.exec("select * from foo.bar"));
})
+ ////////////////////////////////////////////////////////////////////
.t({
- name: 'Jaccwabyt-specific int-pointer tests (if compiled in)',
- predicate: haveJaccwabytTests,
+ name: 'C-side WASM tests (if compiled in)',
+ predicate: haveWasmCTests,
test: function(){
const w = wasm, db = this.db;
const stack = w.scopedAllocPush();
try{
ptrInt = w.scopedAlloc(4);
w.setMemValue(ptrInt,origValue, ptrValType);
- const cf = w.xGet('jaccwabyt_test_intptr');
+ const cf = w.xGet('sqlite3_wasm_test_intptr');
const oldPtrInt = ptrInt;
//log('ptrInt',ptrInt);
//log('getMemValue(ptrInt)',w.getMemValue(ptrInt));
//log("getMemValue(pi64)",v64());
T.assert(v64() == o64);
//T.assert(o64 === w.getMemValue(pi64, ptrType64));
- const cf64w = w.xGet('jaccwabyt_test_int64ptr');
+ const cf64w = w.xGet('sqlite3_wasm_test_int64ptr');
cf64w(pi64);
//log("getMemValue(pi64)",v64());
T.assert(v64() == BigInt(2 * o64));
cf64w(pi64);
T.assert(v64() == BigInt(4 * o64));
- const biTimes2 = w.xGet('jaccwabyt_test_int64_times2');
+ 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 :/ */));
w.setMemValue(pMin, 0, ptrType64);
w.setMemValue(pMax, 0, ptrType64);
const minMaxI64 = [
- w.xCall('jaccwabyt_test_int64_min'),
- w.xCall('jaccwabyt_test_int64_max')
+ 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('jaccwabyt_test_int64_minmax', pMin, pMax);
+ 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));
log("sqlite3 version:",capi.sqlite3_libversion(),
capi.sqlite3_sourceid());
log("BigInt/int64 support is",(wasm.bigIntEnabled ? "enabled" : "disabled"));
- if(haveJaccwabytTests()){
- log("Jaccwabyt test C code found. Jaccwabyt-specific low-level tests.");
+ if(haveWasmCTests()){
+ log("sqlite3_wasm_test_...() APIs are available.");
}
TestUtil.runTests(sqlite3);
});
+++ /dev/null
-/*
- 2022-05-22
-
- 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.
-
- ***********************************************************************
-
- A basic test script for sqlite3-api.js. This file must be run in
- main JS thread and sqlite3.js must have been loaded before it.
-*/
-'use strict';
-(function(){
- const T = self.SqliteTestUtil;
- const toss = function(...args){throw new Error(args.join(' '))};
- const debug = console.debug.bind(console);
- const eOutput = document.querySelector('#test-output');
- const log = console.log.bind(console),
- warn = console.warn.bind(console);
- const logHtml = function(...args){
- log.apply(this, args);
- const ln = document.createElement('div');
- ln.append(document.createTextNode(args.join(' ')));
- eOutput.append(ln);
- };
-
- const eqApprox = function(v1,v2,factor=0.05){
- //debug('eqApprox',v1, v2);
- return v1>=(v2-factor) && v1<=(v2+factor);
- };
-
- let sqlite3 /* loaded later */;
-
- const testBasicSanity = function(db,sqlite3){
- const capi = sqlite3.capi;
- log("Basic sanity tests...");
- T.assert(Number.isInteger(db.pointer)).
- mustThrowMatching(()=>db.pointer=1, /read-only/).
- assert(0===capi.sqlite3_extended_result_codes(db.pointer,1)).
- assert('main'===db.dbName(0));
- let pId;
- let st = db.prepare(
- new TextEncoder('utf-8').encode("select 3 as a")
- /* Testing handling of Uint8Array input */
- );
- //debug("statement =",st);
- try {
- T.assert(Number.isInteger(st.pointer))
- .mustThrowMatching(()=>st.pointer=1, /read-only/)
- .assert(1===db.openStatementCount())
- .assert(!st._mayGet)
- .assert('a' === st.getColumnName(0))
- .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)
- ;
- pId = st.pointer;
- 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));
- }finally{
- st.finalize();
- }
- T.assert(!st.pointer)
- .assert(0===db.openStatementCount());
- let list = [];
- db.exec({
- sql:['CREATE TABLE t(a,b);',
- "INSERT INTO t(a,b) VALUES(1,2),(3,4),",
- "(?,?),('blob',X'6869')"/*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(2 === list.length)
- .assert('string'===typeof list[1])
- .assert(4===db.changes());
- if(capi.wasm.bigIntEnabled){
- T.assert(4n===db.changes(false,true));
- }
- 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,
- callback: function(row,stmt){
- ++counter;
- T.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);
- 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));
- if(capi.wasm.bigIntEnabled){
- const mI = capi.wasm.xCall('jaccwabyt_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)));
- }
-
- st = db.prepare("update t set b=:b where a='blob'");
- try {
- const ndx = st.getParamIndex(':b');
- T.assert(1===ndx);
- st.bindAsBlob(ndx, "ima blob").reset(true);
- } finally {
- st.finalize();
- }
-
- try {
- throw new sqlite3.WasmAllocError;
- }catch(e){
- T.assert(e instanceof Error)
- .assert(e instanceof sqlite3.WasmAllocError);
- }
-
- 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'));
- }
-
- 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');
-
- // Custom db error message handling via sqlite3_prepare_v2/v3()
- if(capi.wasm.exports.sqlite3_wasm_db_error){
- log("Testing custom error message via prepare_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"));
- log("errmsg =",capi.sqlite3_errmsg(db.pointer));
- }
- }/*testBasicSanity()*/;
-
- const testUDF = function(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,
- callback: function(pCx){
- var rc = 0;
- for(let i = 1; i < arguments.length; ++i) rc += arguments[i];
- return rc;
- }
- }).createFunction({
- name: "asis",
- callback: (pCx,arg)=>arg
- });
-
- //log("Testing DB::selectValue() w/ UDF...");
- 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(eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))).
- assert(eqApprox(1.3,db.selectValue("select asis(1 + 0.3)")));
-
- //log("Testing binding and UDF propagation of blobs...");
- let blobArg = new Uint8Array(2);
- blobArg.set([0x68, 0x69], 0);
- let blobRc = db.selectValue("select asis(?1)", blobArg);
- 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(2);
- blobArg.set([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]);
- };
-
- const testAttach = function(db){
- const resultRows = [];
- db.exec({
- sql:new TextEncoder('utf-8').encode([
- // ^^^ testing string-vs-typedarray handling in exec()
- "attach 'session' as foo;" /* name 'session' is magic for kvvfs! */,
- "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'));
- let colCount = 0, rowCount = 0;
- const execCallback = function(pVoid, nCols, aVals, aNames){
- colCount = nCols;
- ++rowCount;
- T.assert(2===aVals.length)
- .assert(2===aNames.length)
- .assert(+(aVals[1]) === 2 * +(aVals[0]));
- };
- const capi = sqlite3.capi;
- let rc = capi.sqlite3_exec(
- db.pointer, "select a, a*2 from foo.bar", execCallback,
- 0, 0
- );
- T.assert(0===rc).assert(3===rowCount).assert(2===colCount);
- rc = capi.sqlite3_exec(
- db.pointer, "select a from foo.bar", ()=>{
- toss("Testing throwing from exec() callback.");
- }, 0, 0
- );
- T.assert(capi.SQLITE_ABORT === rc);
- db.exec("detach foo");
- T.mustThrow(()=>db.exec("select * from foo.bar"));
- };
-
- const testIntPtr = function(db,S){
- const w = S.capi.wasm;
- const stack = w.scopedAllocPush();
- let ptrInt;
- const origValue = 512;
- const ptrValType = 'i32';
- try{
- ptrInt = w.scopedAlloc(4);
- w.setMemValue(ptrInt,origValue, ptrValType);
- const cf = w.xGet('jaccwabyt_test_intptr');
- const oldPtrInt = ptrInt;
- //log('ptrInt',ptrInt);
- //log('getMemValue(ptrInt)',w.getMemValue(ptrInt));
- T.assert(origValue === w.getMemValue(ptrInt, ptrValType));
- const rc = cf(ptrInt);
- //log('cf(ptrInt)',rc);
- //log('ptrInt',ptrInt);
- //log('getMemValue(ptrInt)',w.getMemValue(ptrInt,ptrValType));
- T.assert(2*origValue === rc).
- assert(rc === w.getMemValue(ptrInt,ptrValType)).
- assert(oldPtrInt === ptrInt);
- const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/;
- const o64 = 0x010203040506/*>32-bit integer*/;
- const ptrType64 = 'i64';
- if(w.bigIntEnabled){
- log("BigInt support is enabled...");
- w.setMemValue(pi64, o64, ptrType64);
- //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64);
- const v64 = ()=>w.getMemValue(pi64,ptrType64)
- //log("getMemValue(pi64)",v64());
- T.assert(v64() == o64);
- //T.assert(o64 === w.getMemValue(pi64, ptrType64));
- const cf64w = w.xGet('jaccwabyt_test_int64ptr');
- cf64w(pi64);
- //log("getMemValue(pi64)",v64());
- T.assert(v64() == BigInt(2 * o64));
- cf64w(pi64);
- T.assert(v64() == BigInt(4 * o64));
-
- const biTimes2 = w.xGet('jaccwabyt_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.getMemValue(p,ptrType64);
- w.setMemValue(pMin, 0, ptrType64);
- w.setMemValue(pMax, 0, ptrType64);
- const minMaxI64 = [
- w.xCall('jaccwabyt_test_int64_min'),
- w.xCall('jaccwabyt_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('jaccwabyt_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.setMemValue(pMin, minMaxI64[0], ptrType64);
- 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);
- }
- }/*testIntPtr()*/;
-
- const testStructStuff = function(db,S,M){
- const W = S.capi.wasm, C = S;
- /** Maintenance reminder: the rest of this function is copy/pasted
- from the upstream jaccwabyt tests. */
- log("Jaccwabyt tests...");
- 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 = C.StructBinder.StructType;
- const K = C.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)).
- assert(K.resolveToInstance(k1.pointer)===k1).
- mustThrowMatching(()=>K.resolveToInstance(null,true), /is-not-a my_struct/).
- assert(k1 === StructType.instanceForPointer(k1.pointer)).
- 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/).
- assert(K.instanceForPointer(k1.pointer) === k1);
- 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);
- 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).
- assert(undefined === K.instanceForPointer(ptr)).
- mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
- const k3 = new K();
- ptr = k3.pointer;
- T.assert(k3 === K.instanceForPointer(ptr));
- K.disposeAll();
- T.assert(ptr).
- assert(undefined === k2.pointer).
- assert(undefined === k3.pointer).
- assert(undefined === K.instanceForPointer(ptr));
- }finally{
- k1.dispose();
- k2.dispose();
- }
-
- if(!W.bigIntEnabled){
- log("Skipping WasmTestStruct tests: BigInt not enabled.");
- return;
- }
-
- const ctype = W.xCallWrapped('jaccwabyt_test_ctype_json', 'json');
- log("Struct descriptions:",ctype.structs);
- const WTStructDesc =
- ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0];
- const autoResolvePtr = true /* EXPERIMENTAL */;
- if(autoResolvePtr){
- WTStructDesc.members.ppV.signature = 'P';
- }
- const WTStruct = C.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 === StructType.instanceForPointer(wts.pointer));
- T.assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
- assert(0===wts.$ppV).assert(0===wts.$xFunc).
- assert(WTStruct.instanceForPointer(wts.pointer) === wts);
- const testFunc =
- W.xGet('jaccwabyt_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;
- T.assert(WTStruct.instanceForPointer(arg) === wts);
- if(3===counter){
- toss("Testing exception propagation.");
- }
- }
- wts.$v4 = 10; wts.$v8 = 20;
- wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc'))
- /* ^^^ compiles wtsFunc to WASM and returns its new function pointer */;
- 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(autoResolvePtr ? (wts.$ppV === wts) : (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(autoResolvePtr ? (wts.$ppV === wts) : (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;
- if(autoResolvePtr){
- wts.$ppV = 0;
- T.assert(!wts.$ppV);
- WTStruct.debugFlags(0x03);
- wts.$ppV = wts;
- T.assert(wts === wts.$ppV)
- WTStruct.debugFlags(0);
- }
- 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).
- assert(undefined === WTStruct.instanceForPointer(ptr))
- }finally{
- wts.dispose();
- }
- }/*testStructStuff()*/;
-
- const testSqliteStructs = function(db,sqlite3,M){
- log("Tinkering with sqlite3_io_methods...");
- // https://www.sqlite.org/c3ref/vfs.html
- // https://www.sqlite.org/c3ref/io_methods.html
- const capi = sqlite3.capi, W = capi.wasm;
- const sqlite3_io_methods = capi.sqlite3_io_methods,
- sqlite3_vfs = capi.sqlite3_vfs,
- sqlite3_file = capi.sqlite3_file;
- log("struct sqlite3_file", sqlite3_file.memberKeys());
- log("struct sqlite3_vfs", sqlite3_vfs.memberKeys());
- log("struct sqlite3_io_methods", sqlite3_io_methods.memberKeys());
-
- const installMethod = function callee(tgt, name, func){
- if(1===arguments.length){
- return (n,f)=>callee(tgt,n,f);
- }
- if(!callee.argcProxy){
- callee.argcProxy = function(func,sig){
- return function(...args){
- if(func.length!==arguments.length){
- toss("Argument mismatch. Native signature is:",sig);
- }
- return func.apply(this, args);
- }
- };
- callee.ondisposeRemoveFunc = function(){
- if(this.__ondispose){
- const who = this;
- this.__ondispose.forEach(
- (v)=>{
- if('number'===typeof v){
- try{capi.wasm.uninstallFunction(v)}
- catch(e){/*ignore*/}
- }else{/*wasm function wrapper property*/
- delete who[v];
- }
- }
- );
- delete this.__ondispose;
- }
- };
- }/*static init*/
- const sigN = tgt.memberSignature(name),
- memKey = tgt.memberKey(name);
- //log("installMethod",tgt, name, sigN);
- if(!tgt.__ondispose){
- T.assert(undefined === tgt.ondispose);
- tgt.ondispose = [callee.ondisposeRemoveFunc];
- tgt.__ondispose = [];
- }
- const fProxy = callee.argcProxy(func, sigN);
- const pFunc = capi.wasm.installFunction(fProxy, tgt.memberSignature(name, true));
- tgt[memKey] = pFunc;
- /**
- ACHTUNG: function pointer IDs are from a different pool than
- allocation IDs, starting at 1 and incrementing in steps of 1,
- so if we set tgt[memKey] to those values, we'd very likely
- later misinterpret them as plain old pointer addresses unless
- unless we use some silly heuristic like "all values <5k are
- presumably function pointers," or actually perform a function
- lookup on every pointer to first see if it's a function. That
- would likely work just fine, but would be kludgy.
-
- It turns out that "all values less than X are functions" is
- essentially how it works in wasm: a function pointer is
- reported to the client as its index into the
- __indirect_function_table.
-
- So... once jaccwabyt can be told how to access the
- function table, it could consider all pointer values less
- than that table's size to be functions. As "real" pointer
- values start much, much higher than the function table size,
- that would likely work reasonably well. e.g. the object
- pointer address for sqlite3's default VFS is (in this local
- setup) 65104, whereas the function table has fewer than 600
- entries.
- */
- const wrapperKey = '$'+memKey;
- tgt[wrapperKey] = fProxy;
- tgt.__ondispose.push(pFunc, wrapperKey);
- //log("tgt.__ondispose =",tgt.__ondispose);
- return (n,f)=>callee(tgt, n, f);
- }/*installMethod*/;
-
- const installIOMethods = function instm(iom){
- (iom instanceof capi.sqlite3_io_methods) || toss("Invalid argument type.");
- if(!instm._requireFileArg){
- instm._requireFileArg = function(arg,methodName){
- arg = capi.sqlite3_file.resolveToInstance(arg);
- if(!arg){
- err("sqlite3_io_methods::xClose() was passed a non-sqlite3_file.");
- }
- return arg;
- };
- instm._methods = {
- // https://sqlite.org/c3ref/io_methods.html
- xClose: /*i(P)*/function(f){
- /* int (*xClose)(sqlite3_file*) */
- log("xClose(",f,")");
- if(!(f = instm._requireFileArg(f,'xClose'))) return capi.SQLITE_MISUSE;
- f.dispose(/*noting that f has externally-owned memory*/);
- return 0;
- },
- xRead: /*i(Ppij)*/function(f,dest,n,offset){
- /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
- log("xRead(",arguments,")");
- if(!(f = instm._requireFileArg(f))) return capi.SQLITE_MISUSE;
- capi.wasm.heap8().fill(0, dest + offset, n);
- return 0;
- },
- xWrite: /*i(Ppij)*/function(f,dest,n,offset){
- /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
- log("xWrite(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xWrite'))) return capi.SQLITE_MISUSE;
- return 0;
- },
- xTruncate: /*i(Pj)*/function(f){
- /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */
- log("xTruncate(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xTruncate'))) return capi.SQLITE_MISUSE;
- return 0;
- },
- xSync: /*i(Pi)*/function(f){
- /* int (*xSync)(sqlite3_file*, int flags) */
- log("xSync(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xSync'))) return capi.SQLITE_MISUSE;
- return 0;
- },
- xFileSize: /*i(Pp)*/function(f,pSz){
- /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */
- log("xFileSize(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xFileSize'))) return capi.SQLITE_MISUSE;
- capi.wasm.setMemValue(pSz, 0/*file size*/);
- return 0;
- },
- xLock: /*i(Pi)*/function(f){
- /* int (*xLock)(sqlite3_file*, int) */
- log("xLock(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xLock'))) return capi.SQLITE_MISUSE;
- return 0;
- },
- xUnlock: /*i(Pi)*/function(f){
- /* int (*xUnlock)(sqlite3_file*, int) */
- log("xUnlock(",arguments,")");
- if(!(f=instm._requireFileArg(f,'xUnlock'))) return capi.SQLITE_MISUSE;
- return 0;
- },
- xCheckReservedLock: /*i(Pp)*/function(){
- /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
- log("xCheckReservedLock(",arguments,")");
- return 0;
- },
- xFileControl: /*i(Pip)*/function(){
- /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
- log("xFileControl(",arguments,")");
- return capi.SQLITE_NOTFOUND;
- },
- xSectorSize: /*i(P)*/function(){
- /* int (*xSectorSize)(sqlite3_file*) */
- log("xSectorSize(",arguments,")");
- return 0/*???*/;
- },
- xDeviceCharacteristics:/*i(P)*/function(){
- /* int (*xDeviceCharacteristics)(sqlite3_file*) */
- log("xDeviceCharacteristics(",arguments,")");
- return 0;
- }
- };
- }/*static init*/
- iom.$iVersion = 1;
- Object.keys(instm._methods).forEach(
- (k)=>installMethod(iom, k, instm._methods[k])
- );
- }/*installIOMethods()*/;
-
- const iom = new sqlite3_io_methods, sfile = new sqlite3_file;
- const err = console.error.bind(console);
- try {
- const IOM = sqlite3_io_methods, S3F = sqlite3_file;
- //log("iom proto",iom,iom.constructor.prototype);
- //log("sfile",sfile,sfile.constructor.prototype);
- T.assert(0===sfile.$pMethods).assert(iom.pointer > 0);
- //log("iom",iom);
- sfile.$pMethods = iom.pointer;
- T.assert(iom.pointer === sfile.$pMethods)
- .assert(IOM.resolveToInstance(iom))
- .assert(undefined ===IOM.resolveToInstance(sfile))
- .mustThrow(()=>IOM.resolveToInstance(0,true))
- .assert(S3F.resolveToInstance(sfile.pointer))
- .assert(undefined===S3F.resolveToInstance(iom))
- .assert(iom===IOM.resolveToInstance(sfile.$pMethods));
- T.assert(0===iom.$iVersion);
- installIOMethods(iom);
- T.assert(1===iom.$iVersion);
- //log("iom.__ondispose",iom.__ondispose);
- T.assert(Array.isArray(iom.__ondispose)).assert(iom.__ondispose.length>10);
- }finally{
- iom.dispose();
- T.assert(undefined === iom.__ondispose);
- }
-
- const dVfs = new sqlite3_vfs(capi.sqlite3_vfs_find(null));
- try {
- const SB = sqlite3.StructBinder;
- T.assert(dVfs instanceof SB.StructType)
- .assert(dVfs.pointer)
- .assert('sqlite3_vfs' === dVfs.structName)
- .assert(!!dVfs.structInfo)
- .assert(SB.StructType.hasExternalPointer(dVfs))
- .assert(dVfs.$iVersion>0)
- .assert('number'===typeof dVfs.$zName)
- .assert('number'===typeof dVfs.$xSleep)
- .assert(capi.wasm.functionEntry(dVfs.$xOpen))
- .assert(dVfs.memberIsString('zName'))
- .assert(dVfs.memberIsString('$zName'))
- .assert(!dVfs.memberIsString('pAppData'))
- .mustThrowMatching(()=>dVfs.memberToJsString('xSleep'),
- /Invalid member type signature for C-string/)
- .mustThrowMatching(()=>dVfs.memberSignature('nope'), /nope is not a mapped/)
- .assert('string' === typeof dVfs.memberToJsString('zName'))
- .assert(dVfs.memberToJsString('zName')===dVfs.memberToJsString('$zName'))
- ;
- log("Default VFS: @",dVfs.pointer);
- Object.keys(sqlite3_vfs.structInfo.members).forEach(function(mname){
- const mk = sqlite3_vfs.memberKey(mname), mbr = sqlite3_vfs.structInfo.members[mname],
- addr = dVfs[mk], prefix = 'defaultVfs.'+mname;
- if(1===mbr.signature.length){
- let sep = '?', val = undefined;
- switch(mbr.signature[0]){
- // TODO: move this into an accessor, e.g. getPreferredValue(member)
- case 'i': case 'j': case 'f': case 'd': sep = '='; val = dVfs[mk]; break
- case 'p': case 'P': sep = '@'; val = dVfs[mk]; break;
- case 's': sep = '=';
- //val = capi.wasm.UTF8ToString(addr);
- val = dVfs.memberToJsString(mname);
- break;
- }
- log(prefix, sep, val);
- }
- else{
- log(prefix," = funcptr @",addr, capi.wasm.functionEntry(addr));
- }
- });
- }finally{
- dVfs.dispose();
- T.assert(undefined===dVfs.pointer);
- }
- }/*testSqliteStructs()*/;
-
- const testWasmUtil = function(DB,S){
- const w = S.capi.wasm;
- /**
- Maintenance reminder: the rest of this function is part of the
- upstream Jaccwabyt tree.
- */
- 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);
- }
- }
-
- 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.cstringToJs(cpy)).
- assert(chr('o') === w.getMemValue(cpy+n-1)).
- assert(0 === w.getMemValue(cpy+n));
- let cStr2 = w.scopedAllocCString("HI!!!");
- rc = w.cstrncpy(cpy, cStr2, 3);
- T.assert(3===rc).
- assert("HI!lo" === w.cstringToJs(cpy)).
- assert(chr('!') === w.getMemValue(cpy+2)).
- assert(chr('l') === w.getMemValue(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 cstr = w.allocCString("hällo, world");
- const n = w.cstrlen(cstr);
- T.assert(13 === n)
- .assert(0===w.getMemValue(cstr+n))
- .assert(chr('d')===w.getMemValue(cstr+n-1));
- }
-
- 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.getMemValue(z1,'i32'), 'allocPtr() must zero the targets')
- .assert(0===w.getMemValue(z3,'i32'));
- }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.getMemValue(cstr+n))
- .assert(chr('d')===w.getMemValue(cstr+n-1));
- });
- }/*scopedAlloc()*/
-
- log("xCall()...");
- {
- const pJson = w.xCall('jaccwabyt_test_ctype_json');
- T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
- }
-
- log("xWrap()...");
- {
- //int jaccwabyt_test_intptr(int * p);
- //int64_t jaccwabyt_test_int64_max(void)
- //int64_t jaccwabyt_test_int64_min(void)
- //int64_t jaccwabyt_test_int64_times2(int64_t x)
- //void jaccwabyt_test_int64_minmax(int64_t * min, int64_t *max)
- //int64_t jaccwabyt_test_int64ptr(int64_t * p)
- //const char * jaccwabyt_test_ctype_json(void)
- T.mustThrowMatching(()=>w.xWrap('jaccwabyt_test_ctype_json',null,'i32'),
- /requires 0 arg/).
- assert(w.xWrap.resultAdapter('i32') instanceof Function).
- assert(w.xWrap.argAdapter('i32') instanceof Function);
- let fw = w.xWrap('jaccwabyt_test_ctype_json','string');
- T.mustThrowMatching(()=>fw(1), /requires 0 arg/);
- let rc = fw();
- T.assert('string'===typeof rc).assert(rc.length>300);
- rc = w.xCallWrapped('jaccwabyt_test_ctype_json','*');
- T.assert(rc>0 && Number.isFinite(rc));
- rc = w.xCallWrapped('jaccwabyt_test_ctype_json','string');
- T.assert('string'===typeof rc).assert(rc.length>300);
- fw = w.xWrap('jaccwabyt_test_str_hello', 'string:free',['i32']);
- rc = fw(0);
- T.assert('hello'===rc);
- rc = fw(1);
- T.assert(null===rc);
-
- w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v));
- w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v));
- fw = w.xWrap('jaccwabyt_test_int64_times2','thrice','twice');
- rc = fw(1);
- T.assert(12n===rc);
-
- w.scopedAllocCall(function(){
- let pI1 = w.scopedAlloc(8), pI2 = pI1+4;
- w.setMemValue(pI1, 0,'*')(pI2, 0, '*');
- let f = w.xWrap('jaccwabyt_test_int64_minmax',undefined,['i64*','i64*']);
- let r1 = w.getMemValue(pI1, 'i64'), r2 = w.getMemValue(pI2, 'i64');
- T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2));
- });
- }
- }/*testWasmUtil()*/;
-
-
- /**
- Tests for sqlite3.capi.wasm.pstack().
- */
- const testPstack = function(db,sqlite3){
- const w = sqlite3.capi.wasm, P = w.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;
- log("pstack quota, remaining",P.quota,remaining);
- T.assert(P.quota >= 4096)
- .assert(remaining === P.quota)
- .mustThrowMatching(()=>P.alloc(0), isAllocErr)
- .mustThrowMatching(()=>P.alloc(-1), isAllocErr);
- 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,4);
- 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);
- }
- }/*testPstack()*/;
-
- const clearKvvfs = function(){
- const sz = sqlite3.capi.sqlite3_web_kvvfs_size();
- const n = sqlite3.capi.sqlite3_web_kvvfs_clear('');
- log("Cleared kvvfs local/sessionStorage:",
- n,"entries totaling approximately",sz,"bytes.");
- };
-
- const runTests = function(_sqlite3){
- sqlite3 = _sqlite3;
- const capi = sqlite3.capi,
- oo = sqlite3.oo1,
- wasm = capi.wasm;
- log("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
- log("Build options:",wasm.compileOptionUsed());
- capi.sqlite3_wasmfs_opfs_dir()/*will install OPFS if available, plus a and non-locking VFS*/;
- if(1){
- /* Let's grab those last few lines of test coverage for
- sqlite3-api.js... */
- const rc = wasm.compileOptionUsed(['COMPILER']);
- T.assert(1 === rc.COMPILER);
- const obj = {COMPILER:undefined};
- wasm.compileOptionUsed(obj);
- T.assert(1 === obj.COMPILER);
- }
- log("WASM heap size =",wasm.heap8().length);
- //log("capi.wasm.exports.__indirect_function_table",capi.wasm.exports.__indirect_function_table);
-
- const wasmCtypes = wasm.ctype;
- //log("wasmCtypes",wasmCtypes);
- 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);
- //log(wasmCtypes.structs[0].name,"members",wasmCtypes.structs[0].members);
- [ /* 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(function(k){
- T.assert('number' === typeof capi[k]);
- });
- [/* Spot-check a few of the WASM API methods. */
- 'alloc', 'dealloc', 'installFunction'
- ].forEach(function(k){
- T.assert(capi.wasm[k] instanceof Function);
- });
-
- let dbName = "/testing1.sqlite3";
- let vfsName = undefined;
- if(capi.sqlite3_web_db_uses_vfs(0,"kvvfs")){
- dbName = "local";
- vfsName = 'kvvfs';
- logHtml("Found kvvfs. Clearing db(s) from sessionStorage and localStorage",
- "and selecting kvvfs-friendly db name:",dbName);
- clearKvvfs();
- }
- const db = new oo.DB(dbName,'c',vfsName), startTime = performance.now();
- log("db is kvvfs?",capi.sqlite3_web_db_uses_vfs(db.pointer,"kvvfs"));
- try {
- log("db.filename =",db.filename,"db.fileName() =",db.getFilename());
- const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>',
- banner2 = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<';
- [
- testWasmUtil, testBasicSanity, testUDF,
- testAttach, testIntPtr, testStructStuff,
- testSqliteStructs, testPstack
- ].forEach((f)=>{
- const t = T.counter, n = performance.now();
- logHtml(banner1,"Running",f.name+"()...");
- f(db, sqlite3);
- logHtml(banner2,f.name+"():",T.counter - t,'tests in',(performance.now() - n),"ms");
- });
- }finally{
- db.close();
- if('kvvfs'===vfsName) clearKvvfs();
- }
- logHtml("Total Test count:",T.counter,"in",(performance.now() - startTime),"ms");
- log('capi.wasm.exports',capi.wasm.exports);
- };
-
- self.sqlite3TestModule.initSqlite3().then((S)=>{
- runTests(S);
- });
-})();
-C Optimize\sthe\sIS\sNULL\sand\sIS\sNOT\sNULL\soperators\sso\sthat\sthey\savoid\sloading\nlarge\sstrings\sor\sblobs\soff\sof\sdisk\sif\sall\sit\sneeds\sto\sknow\sis\swhether\sor\nnot\sthe\sstring\sor\sblob\sis\sNULL.
-D 2022-10-13T15:09:44.252
+C Move\sthe\srest\sof\stesting1.js\sinto\stester1.js\sand\seliminate\sthe\sdependency\son\sjaccwabyt_test.c.\sExtend\sthe\slist\sof\sdefault\sconfig-related\s#defines\sin\ssqlite3-wasm.c\sand\sreorganize\sthem\sfor\smaintainability.
+D 2022-10-13T16:48:35.302
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb
F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c
F ext/wasm/EXPORTED_RUNTIME_METHODS.fiddle 0e88c8cfc3719e4b7e74980d9da664c709e68acf863e48386cda376edfd3bfb0
-F ext/wasm/GNUmakefile 8ab74ed186a15d956a21b28fa0800b84af2b8a289392ae2dff8126ff033bd3f9
+F ext/wasm/GNUmakefile 4ec270532b921c7c4b437fbdb06f6a0ce41f3bd874395ce70dbc933c8553efa9
F ext/wasm/README.md 1e5b28158b74ab3ffc9d54fcbc020f0bbeb82c2ff8bbd904214c86c70e8a3066
F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 89983a8d122c35a90c65ec667844b95a78bcd04f3198a99c1e0c8368c1a0b03a
F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
F ext/wasm/api/post-js-header.js 2e5c886398013ba2af88028ecbced1e4b22dc96a86467f1ecc5ba9e64ef90a8b
F ext/wasm/api/pre-js.js 2db711eb637991b383fc6b5c0f3df65ec48a7201e5730e304beba8de2d3f9b0b
F ext/wasm/api/sqlite3-api-cleanup.js 5d22d1d3818ecacb23bfa223d5970cd0617d8cdbb48c8bc4bbd463f05b021a99
-F ext/wasm/api/sqlite3-api-glue.js 3b2a43c7b2ceb2b60f5f4a1afefbd93508c2fe0f2e667eea278afe570be4b606
-F ext/wasm/api/sqlite3-api-oo1.js ac1e08d36bdfb5aa0a2d75b7d4c892fd51819d34c932370c3282810672bcc086
+F ext/wasm/api/sqlite3-api-glue.js 842dc03783aecc951a543a209523343a6fda9af258fb0bee08e8cc2dc3c4d8ae
+F ext/wasm/api/sqlite3-api-oo1.js 00f5cfce0989d2e08d7b21765d703c69234245d03a0cce8fcb32ccfcd53ffdbb
F ext/wasm/api/sqlite3-api-opfs.js 5a8ab3b76880c8ada8710ca9ba1ca5b160872edfd8bd5322e4f179a7f41cc616
-F ext/wasm/api/sqlite3-api-prologue.js 5c56056810333974c971f6310c5c9698cf7ca8b06f6d2f1986cec819d0f5bbad
+F ext/wasm/api/sqlite3-api-prologue.js b7c82a22d50658a48463fa646a23135273bc2cfa843aedda32627ff281c12e4d
F ext/wasm/api/sqlite3-api-worker1.js 7f4f46cb6b512a48572d7567233896e6a9c46570c44bdc3d13419730c7c221c8
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
-F ext/wasm/api/sqlite3-wasm.c a321f12ceedac8611c1377ccfb5df0c0547bd9395f7fd7613827de365d994948
+F ext/wasm/api/sqlite3-wasm.c 4c131945ced4b08a694d287abcdb066b896d961ef79ee5241805ecc37e83d63a
F ext/wasm/batch-runner.html cf1a410c92bad50fcec2ddc71390b4e9df63a6ea1bef12a5163a66a0af4d78d9
F ext/wasm/batch-runner.js 5bae81684728b6be157d1f92b39824153f0fd019345b39f2ab8930f7ee2a57d8
F ext/wasm/common/SqliteTestUtil.js 647bf014bd30bdd870a7e9001e251d12fc1c9ec9ce176a1004b838a4b33c5c05
F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
-F ext/wasm/common/testing.css 12bd88c69eea1b61649ad9add3ff24f0fbc3b79a7616d74ab3997b7271cd8482
-F ext/wasm/common/whwasmutil.js bc8522a071f4754af7b50f53807b95f691d2f9e44fc3b3e8c65dff6ef2485c0d
+F ext/wasm/common/testing.css 53394885077edd3db22d2a0896192334dfc06fb3d1da0b646eb12a332d22f18e
+F ext/wasm/common/whwasmutil.js 50d2ede0b0fa01c1d467e1801fab79f5e46bb02bcbd2b0232e4fdc6090a47818
F ext/wasm/demo-123-worker.html e50b51dc7271b9d3cc830cb7c2fba294d622f56b7acb199f7257d11195a63d49
F ext/wasm/demo-123.html 7c239c9951d1b113f9f532969ac039294cf1dcfee2b3ae0a2c1ed2b3d59f8dfa
F ext/wasm/demo-123.js d563cf9d725692ccd940c46df1c026d87863e0544942a2ba2015f17fba3f6f74
F ext/wasm/fiddle/fiddle-worker.js 531859a471924a0ea48afa218e6877f0c164ca324d51e15843ed6ecc1c65c7ee
F ext/wasm/fiddle/fiddle.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2
F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5653284071715
-F ext/wasm/index.html 63b370619e4f849ac76f1baed435c05edc29dbb6795bc7c1c935561ff667dd27
+F ext/wasm/index.html 9ae9f9629310ed3ee88901aa14e20195815583806af245da08c2ecb43ced2243
F ext/wasm/jaccwabyt/jaccwabyt.js 0d7f32817456a0f3937fcfd934afeb32154ca33580ab264dab6c285e6dbbd215
F ext/wasm/jaccwabyt/jaccwabyt.md 9aa6951b529a8b29f578ec8f0355713c39584c92cf1708f63ba0cf917cb5b68e
-F ext/wasm/jaccwabyt/jaccwabyt_test.c 39e4b865a33548f943e2eb9dd0dc8d619a80de05d5300668e9960fff30d0d36f
-F ext/wasm/jaccwabyt/jaccwabyt_test.exports 5ff001ef975c426ffe88d7d8a6e96ec725e568d2c2307c416902059339c06f19
F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06
F ext/wasm/scratchpad-wasmfs-main.js 1aa32c1035cf1440a226a28fefcbb5762fbbcb020ccbe5895f8736d701695c63
F ext/wasm/speedtest1-wasmfs.html bc28eb29b69a73864b8d7aae428448f8b7e1de81d8bfb9bba99541322054dbd0
F ext/wasm/test-opfs-vfs.js 56c3d725044c668fa7910451e96c1195d25ad95825f9ac79f747a7759d1973d0
F ext/wasm/tester1-worker.html 0af7a22025ff1da72a84765d64f8f221844a57c6e6e314acf3a30f176101fd3f
F ext/wasm/tester1.html fde0e0bdeaaa2c39877c749dc86a8c1c306f771c3d75b89a6289a5ed11243e9d
-F ext/wasm/tester1.js d25cf7615bd39ab9ab4be2f231fecf1fff1378227e695083dc35adc4fdff668e
+F ext/wasm/tester1.js d6a66fc36c7571b8050314b6d0e93e69cd6def88df14fb3ef2403e7aa244ec36
F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893
F ext/wasm/testing-worker1-promiser.js bd788e33c1807e0a6dda9c9a9d784bd3350ca49c9dd8ae2cc8719b506b6e013e
F ext/wasm/testing1.html 50575755e43232dbe4c2f97c9086b3118eb91ec2ee1fae931e6d7669fb17fcae
-F ext/wasm/testing1.js 2034cf6972ab1506e75e41e532ca7cd3060e194c37edab3ff65d00d8a8af8ba2
F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3
F ext/wasm/testing2.js 88f40ef3cd8201bdadd120a711c36bbf0ce56cc0eab1d5e7debb71fed7822494
F ext/wasm/wasmfs.make 3cce1820006196de140f90f2da4b4ea657083fb5bfee7d125be43f7a85748c8f
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 043e76e6166da5cf8e213cce46aaccb1f910e1fdbdb5556576eafb81b3bc5faa 5e9c67ba18b701aabbb0546acdfc532c9e8f0d27fb0a2c899415a5c47096c90b
-R c0d9c663bdd4b8318254b199c65d7e13
-T +closed 5e9c67ba18b701aabbb0546acdfc532c9e8f0d27fb0a2c899415a5c47096c90b
-U drh
-Z 57cc74128fdf4ddaf1ca461d6df422e4
+P cb94350185f555c333b628ee846c47bcc9df5f76bb82de569b8322f30dbbe1bc
+R 32dab828066657d904ede486bb592456
+U stephan
+Z 3fd1b23c49b42c2df54d60ffd9b89d7f
# Remove this line to create a well-formed Fossil manifest.
-cb94350185f555c333b628ee846c47bcc9df5f76bb82de569b8322f30dbbe1bc
\ No newline at end of file
+4e2a8aff2dd4b6e148f45184e2523ebe47815257eca97fa3d32bcbf9625f0def
\ No newline at end of file