1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-11-11 01:42:22 +03:00

Merge recent trunk changes into the coroutine-exp2 branch.

FossilOrigin-Name: c43f433bcab29db0f1f8afd3948f5a4149e1f277c853c66f99c31f226d82bc0f
This commit is contained in:
drh
2022-12-13 00:51:34 +00:00
34 changed files with 3739 additions and 1618 deletions

View File

@@ -48,7 +48,7 @@ do_test 1.2 {
do_test 1.3 { do_test 1.3 {
set ::errlog set ::errlog
} {SQLITE_NOTICE_RECOVER_WAL SQLITE_INTERNAL} } {SQLITE_NOTICE_RECOVER_WAL SQLITE_NOTICE_RBU}
do_execsql_test 1.4 { do_execsql_test 1.4 {
SELECT * FROM t1 SELECT * FROM t1

View File

@@ -3031,11 +3031,11 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){
** no-ops. These locks will not be released until the connection ** no-ops. These locks will not be released until the connection
** is closed. ** is closed.
** **
** * Attempting to xSync() the database file causes an SQLITE_INTERNAL ** * Attempting to xSync() the database file causes an SQLITE_NOTICE
** error. ** error.
** **
** As a result, unless an error (i.e. OOM or SQLITE_BUSY) occurs, the ** As a result, unless an error (i.e. OOM or SQLITE_BUSY) occurs, the
** checkpoint below fails with SQLITE_INTERNAL, and leaves the aFrame[] ** checkpoint below fails with SQLITE_NOTICE, and leaves the aFrame[]
** array populated with a set of (frame -> page) mappings. Because the ** array populated with a set of (frame -> page) mappings. Because the
** WRITER, CHECKPOINT and READ0 locks are still held, it is safe to copy ** WRITER, CHECKPOINT and READ0 locks are still held, it is safe to copy
** data from the wal file into the database file according to the ** data from the wal file into the database file according to the
@@ -3045,7 +3045,7 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){
int rc2; int rc2;
p->eStage = RBU_STAGE_CAPTURE; p->eStage = RBU_STAGE_CAPTURE;
rc2 = sqlite3_exec(p->dbMain, "PRAGMA main.wal_checkpoint=restart", 0, 0,0); rc2 = sqlite3_exec(p->dbMain, "PRAGMA main.wal_checkpoint=restart", 0, 0,0);
if( rc2!=SQLITE_INTERNAL ) p->rc = rc2; if( rc2!=SQLITE_NOTICE ) p->rc = rc2;
} }
if( p->rc==SQLITE_OK && p->nFrame>0 ){ if( p->rc==SQLITE_OK && p->nFrame>0 ){
@@ -3091,7 +3091,7 @@ static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){
if( pRbu->mLock!=mReq ){ if( pRbu->mLock!=mReq ){
pRbu->rc = SQLITE_BUSY; pRbu->rc = SQLITE_BUSY;
return SQLITE_INTERNAL; return SQLITE_NOTICE_RBU;
} }
pRbu->pgsz = iAmt; pRbu->pgsz = iAmt;
@@ -4478,7 +4478,7 @@ void sqlite3rbu_rename_handler(
** database file are recorded. xShmLock() calls to unlock the same ** database file are recorded. xShmLock() calls to unlock the same
** locks are no-ops (so that once obtained, these locks are never ** locks are no-ops (so that once obtained, these locks are never
** relinquished). Finally, calls to xSync() on the target database ** relinquished). Finally, calls to xSync() on the target database
** file fail with SQLITE_INTERNAL errors. ** file fail with SQLITE_NOTICE errors.
*/ */
static void rbuUnlockShm(rbu_file *p){ static void rbuUnlockShm(rbu_file *p){
@@ -4757,7 +4757,7 @@ static int rbuVfsSync(sqlite3_file *pFile, int flags){
rbu_file *p = (rbu_file *)pFile; rbu_file *p = (rbu_file *)pFile;
if( p->pRbu && p->pRbu->eStage==RBU_STAGE_CAPTURE ){ if( p->pRbu && p->pRbu->eStage==RBU_STAGE_CAPTURE ){
if( p->openFlags & SQLITE_OPEN_MAIN_DB ){ if( p->openFlags & SQLITE_OPEN_MAIN_DB ){
return SQLITE_INTERNAL; return SQLITE_NOTICE_RBU;
} }
return SQLITE_OK; return SQLITE_OK;
} }

View File

@@ -15,9 +15,17 @@
# by the target name. Rebuild is necessary for all components to get # by the target name. Rebuild is necessary for all components to get
# the desired optimization level. # the desired optimization level.
# #
# quick, q = do just a minimal build (sqlite3.js/wasm, tester1) for
# faster development-mode turnaround.
#
# qo2, qoz = a combination of quick+o2/oz.
#
# dist = create end user deliverables. Add dist.build=oX to build # dist = create end user deliverables. Add dist.build=oX to build
# with a specific optimization level, where oX is one of the # with a specific optimization level, where oX is one of the
# above-listed o? target names. # above-listed o? or qo? target names.
#
# snapshot = like dist, but uses a zip file name which clearly
# marks it as a prerelease/snapshot build.
# #
# clean = clean up # clean = clean up
# #
@@ -289,7 +297,7 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.js
sqlite3-api.jses += $(sqlite3-api-build-version.js) sqlite3-api.jses += $(sqlite3-api-build-version.js)
sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js
sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.js sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js
sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js
@@ -307,7 +315,8 @@ $$(dir.dout)/$$(notdir $(1)): $(1) $$(MAKEFILE)
endef endef
$(foreach X,$(SOAP.js) $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js),\ $(foreach X,$(SOAP.js) $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js),\
$(eval $(call COPY_XAPI,$(X)))) $(eval $(call COPY_XAPI,$(X))))
all: $(sqlite3-api.ext.jses) all quick: $(sqlite3-api.ext.jses)
q: quick
# sqlite3-api.js.in = the generated sqlite3-api.js before it gets # sqlite3-api.js.in = the generated sqlite3-api.js before it gets
# preprocessed. It contains all of $(sqlite3-api.jses) but none of the # preprocessed. It contains all of $(sqlite3-api.jses) but none of the
@@ -488,7 +497,7 @@ emcc.jsflags += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY))
######################################################################## ########################################################################
emcc.jsflags += $(emcc.environment) emcc.jsflags += $(emcc.environment)
emcc.jsflags += -sSTACK_SIZE=1MB emcc.jsflags += -sSTACK_SIZE=512KB
# ^^^ ACHTUNG: emsdk 3.1.27 reduced the default stack size from 5MB to # ^^^ ACHTUNG: emsdk 3.1.27 reduced the default stack size from 5MB to
# a mere 64KB, which leads to silent memory corruption via the kvvfs # a mere 64KB, which leads to silent memory corruption via the kvvfs
# VFS, which requires twice that for its xRead() and xWrite() methods. # VFS, which requires twice that for its xRead() and xWrite() methods.
@@ -627,8 +636,9 @@ $(sqlite3.mjs):
$(sqlite3.wasm): $(sqlite3.js) $(sqlite3.wasm): $(sqlite3.js)
$(sqlite3.mjs): $(sqlite3.js) $(sqlite3.mjs): $(sqlite3.js)
CLEAN_FILES += $(sqlite3.js) $(sqlite3.mjs) $(sqlite3.wasm) CLEAN_FILES += $(sqlite3.js) $(sqlite3.mjs) $(sqlite3.wasm)
all: $(sqlite3.mjs) all: $(sqlite3.js) $(sqlite3.mjs)
wasm: $(sqlite3.mjs) quick: $(sqlite3.js)
quick: $(sqlite3.mjs) # for the sake of the snapshot build
# End main $(sqlite3.js) build # End main $(sqlite3.js) build
######################################################################## ########################################################################
@@ -673,7 +683,7 @@ speedtest1.eflags.common += -sSTRICT_JS
speedtest1.eflags.common += -sMODULARIZE speedtest1.eflags.common += -sMODULARIZE
speedtest1.eflags.common += -Wno-limited-postlink-optimizations speedtest1.eflags.common += -Wno-limited-postlink-optimizations
EXPORTED_FUNCTIONS.speedtest1 := $(abspath $(dir.tmp)/EXPORTED_FUNCTIONS.speedtest1) EXPORTED_FUNCTIONS.speedtest1 := $(abspath $(dir.tmp)/EXPORTED_FUNCTIONS.speedtest1)
speedtest1.eflags.common += -sSTACK_SIZE=1MB speedtest1.eflags.common += -sSTACK_SIZE=512KB
speedtest1.eflags.common += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.speedtest1) speedtest1.eflags.common += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.speedtest1)
speedtest1.eflags.common += $(emcc.exportedRuntimeMethods) speedtest1.eflags.common += $(emcc.exportedRuntimeMethods)
speedtest1.eflags.common += -sALLOW_TABLE_GROWTH speedtest1.eflags.common += -sALLOW_TABLE_GROWTH
@@ -748,7 +758,7 @@ $(eval $(call C-PP.FILTER,tester1.c-pp.js,tester1.mjs,$(c-pp.D.esm)))
$(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1.html)) $(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1.html))
$(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1-esm.html,$(c-pp.D.esm))) $(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1-esm.html,$(c-pp.D.esm)))
tester1: tester1.js tester1.mjs tester1.html tester1-esm.html tester1: tester1.js tester1.mjs tester1.html tester1-esm.html
all: tester1 all quick: tester1
######################################################################## ########################################################################
# Convenience rules to rebuild with various -Ox levels. Much # Convenience rules to rebuild with various -Ox levels. Much
@@ -757,7 +767,8 @@ all: tester1
# painful. # painful.
.PHONY: o0 o1 o2 o3 os oz .PHONY: o0 o1 o2 o3 os oz
o-xtra := -flto o-xtra :=
#o-xtra ?= -flto
# ^^^^ -flto can have a considerably performance boost at -O0 but # ^^^^ -flto can have a considerably performance boost at -O0 but
# doubles the build time and seems to have negligible, if any, effect # doubles the build time and seems to have negligible, if any, effect
# on higher optimization levels. # on higher optimization levels.
@@ -766,14 +777,18 @@ o0: clean
o1: clean o1: clean
$(MAKE) -e "emcc_opt=-O1 $(o-xtra)" $(MAKE) -e "emcc_opt=-O1 $(o-xtra)"
o2: clean o2: clean
$(MAKE) -e "emcc_opt=-O2 $(o-xtra)" $(MAKE) -j2 -e "emcc_opt=-O2 $(o-xtra)"
qo2: clean
$(MAKE) -j2 -e "emcc_opt=-O2 $(o-xtra)" quick
o3: clean o3: clean
$(MAKE) -e "emcc_opt=-O3 $(o-xtra)" $(MAKE) -e "emcc_opt=-O3 $(o-xtra)"
os: clean os: clean
@echo "WARNING: -Os can result in a build with mysteriously missing pieces!" @echo "WARNING: -Os can result in a build with mysteriously missing pieces!"
$(MAKE) -e "emcc_opt=-Os $(o-xtra)" $(MAKE) -e "emcc_opt=-Os $(o-xtra)"
oz: clean oz: clean
$(MAKE) -e "emcc_opt=-Oz $(o-xtra)" $(MAKE) -j2 -e "emcc_opt=-Oz $(o-xtra)"
qoz: clean
$(MAKE) -j2 -e "emcc_opt=-Oz $(o-xtra)" quick
######################################################################## ########################################################################
# Sub-makes... # Sub-makes...

View File

@@ -6,12 +6,15 @@ _sqlite3_bind_int64
_sqlite3_bind_null _sqlite3_bind_null
_sqlite3_bind_parameter_count _sqlite3_bind_parameter_count
_sqlite3_bind_parameter_index _sqlite3_bind_parameter_index
_sqlite3_bind_pointer
_sqlite3_bind_text _sqlite3_bind_text
_sqlite3_busy_handler
_sqlite3_busy_timeout _sqlite3_busy_timeout
_sqlite3_changes _sqlite3_changes
_sqlite3_changes64 _sqlite3_changes64
_sqlite3_clear_bindings _sqlite3_clear_bindings
_sqlite3_close_v2 _sqlite3_close_v2
_sqlite3_collation_needed
_sqlite3_column_blob _sqlite3_column_blob
_sqlite3_column_bytes _sqlite3_column_bytes
_sqlite3_column_count _sqlite3_column_count
@@ -22,16 +25,26 @@ _sqlite3_column_int64
_sqlite3_column_name _sqlite3_column_name
_sqlite3_column_text _sqlite3_column_text
_sqlite3_column_type _sqlite3_column_type
_sqlite3_column_value
_sqlite3_compileoption_get _sqlite3_compileoption_get
_sqlite3_compileoption_used _sqlite3_compileoption_used
_sqlite3_complete
_sqlite3_create_collation
_sqlite3_create_collation_v2
_sqlite3_create_function _sqlite3_create_function
_sqlite3_create_function_v2 _sqlite3_create_function_v2
_sqlite3_create_module
_sqlite3_create_module_v2
_sqlite3_create_window_function _sqlite3_create_window_function
_sqlite3_data_count _sqlite3_data_count
_sqlite3_db_filename _sqlite3_db_filename
_sqlite3_db_handle _sqlite3_db_handle
_sqlite3_db_name _sqlite3_db_name
_sqlite3_db_status
_sqlite3_declare_vtab
_sqlite3_deserialize _sqlite3_deserialize
_sqlite3_drop_modules
_sqlite3_errcode
_sqlite3_errmsg _sqlite3_errmsg
_sqlite3_error_offset _sqlite3_error_offset
_sqlite3_errstr _sqlite3_errstr
@@ -42,16 +55,24 @@ _sqlite3_extended_result_codes
_sqlite3_file_control _sqlite3_file_control
_sqlite3_finalize _sqlite3_finalize
_sqlite3_free _sqlite3_free
_sqlite3_get_auxdata
_sqlite3_initialize _sqlite3_initialize
_sqlite3_keyword_count
_sqlite3_keyword_name
_sqlite3_keyword_check
_sqlite3_last_insert_rowid
_sqlite3_libversion _sqlite3_libversion
_sqlite3_libversion_number _sqlite3_libversion_number
_sqlite3_limit
_sqlite3_malloc _sqlite3_malloc
_sqlite3_malloc64 _sqlite3_malloc64
_sqlite3_msize _sqlite3_msize
_sqlite3_open _sqlite3_open
_sqlite3_open_v2 _sqlite3_open_v2
_sqlite3_overload_function
_sqlite3_prepare_v2 _sqlite3_prepare_v2
_sqlite3_prepare_v3 _sqlite3_prepare_v3
_sqlite3_progress_handler
_sqlite3_randomness _sqlite3_randomness
_sqlite3_realloc _sqlite3_realloc
_sqlite3_realloc64 _sqlite3_realloc64
@@ -65,19 +86,32 @@ _sqlite3_result_error_toobig
_sqlite3_result_int _sqlite3_result_int
_sqlite3_result_int64 _sqlite3_result_int64
_sqlite3_result_null _sqlite3_result_null
_sqlite3_result_pointer
_sqlite3_result_subtype
_sqlite3_result_text _sqlite3_result_text
_sqlite3_result_zeroblob _sqlite3_result_zeroblob
_sqlite3_result_zeroblob64 _sqlite3_result_zeroblob64
_sqlite3_serialize _sqlite3_serialize
_sqlite3_set_auxdata
_sqlite3_set_last_insert_rowid
_sqlite3_shutdown _sqlite3_shutdown
_sqlite3_sourceid _sqlite3_sourceid
_sqlite3_sql _sqlite3_sql
_sqlite3_status
_sqlite3_status64
_sqlite3_step _sqlite3_step
_sqlite3_stmt_isexplain
_sqlite3_stmt_readonly
_sqlite3_stmt_status
_sqlite3_strglob _sqlite3_strglob
_sqlite3_stricmp
_sqlite3_strlike _sqlite3_strlike
_sqlite3_strnicmp
_sqlite3_table_column_metadata
_sqlite3_total_changes _sqlite3_total_changes
_sqlite3_total_changes64 _sqlite3_total_changes64
_sqlite3_trace_v2 _sqlite3_trace_v2
_sqlite3_txn_state
_sqlite3_uri_boolean _sqlite3_uri_boolean
_sqlite3_uri_int64 _sqlite3_uri_int64
_sqlite3_uri_key _sqlite3_uri_key
@@ -86,13 +120,28 @@ _sqlite3_user_data
_sqlite3_value_blob _sqlite3_value_blob
_sqlite3_value_bytes _sqlite3_value_bytes
_sqlite3_value_double _sqlite3_value_double
_sqlite3_value_dup
_sqlite3_value_free
_sqlite3_value_frombind
_sqlite3_value_int _sqlite3_value_int
_sqlite3_value_int64 _sqlite3_value_int64
_sqlite3_value_nochange
_sqlite3_value_numeric_type
_sqlite3_value_pointer
_sqlite3_value_subtype
_sqlite3_value_text _sqlite3_value_text
_sqlite3_value_type _sqlite3_value_type
_sqlite3_vfs_find _sqlite3_vfs_find
_sqlite3_vfs_register _sqlite3_vfs_register
_sqlite3_vfs_unregister _sqlite3_vfs_unregister
_sqlite3_vtab_collation
_sqlite3_vtab_distinct
_sqlite3_vtab_in
_sqlite3_vtab_in_first
_sqlite3_vtab_in_next
_sqlite3_vtab_nochange
_sqlite3_vtab_on_conflict
_sqlite3_vtab_rhs_value
_malloc _malloc
_free _free
_realloc _realloc

View File

@@ -78,11 +78,10 @@ browser client:
a Promise-based interface into the Worker #1 API. This is a Promise-based interface into the Worker #1 API. This is
a far user-friendlier way to interface with databases running a far user-friendlier way to interface with databases running
in a Worker thread. in a Worker thread.
- **`sqlite3-vfs-helper.js`**\ - **`sqlite3-v-helper.js`**\
This internal-use-only file installs `sqlite3.VfsHelper` for use by Installs `sqlite3.VfsHelper` and `sqlite3.VtabHelper` for use by
`sqlite3-*.js` files which create `sqlite3_vfs` implementations. downstream code which creates `sqlite3_vfs` and `sqlite3_module`
`sqlite3.VfsHelper` gets removed from the the `sqlite3` object after implementations.
the library is finished initializing.
- **`sqlite3-vfs-opfs.c-pp.js`**\ - **`sqlite3-vfs-opfs.c-pp.js`**\
is an sqlite3 VFS implementation which supports Google Chrome's is an sqlite3 VFS implementation which supports Google Chrome's
Origin-Private FileSystem (OPFS) as a storage layer to provide Origin-Private FileSystem (OPFS) as a storage layer to provide

View File

@@ -24,6 +24,257 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
self.WhWasmUtilInstaller(wasm); self.WhWasmUtilInstaller(wasm);
delete self.WhWasmUtilInstaller; delete self.WhWasmUtilInstaller;
/**
Signatures for the WASM-exported C-side functions. Each entry
is an array with 2+ elements:
[ "c-side name",
"result type" (wasm.xWrap() syntax),
[arg types in xWrap() syntax]
// ^^^ this needn't strictly be an array: it can be subsequent
// elements instead: [x,y,z] is equivalent to x,y,z
]
Note that support for the API-specific data types in the
result/argument type strings gets plugged in at a later phase in
the API initialization process.
*/
wasm.bindingSignatures = [
// Please keep these sorted by function name!
["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"],
["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*"
/* TODO: we should arguably write a custom wrapper which knows
how to handle Blob, TypedArrays, and JS strings. */
],
["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"],
["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"],
["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"],
["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"],
["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"],
["sqlite3_bind_pointer", "int",
"sqlite3_stmt*", "int", "*", "string:static", "*"],
["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int"
/* We should arguably create a hand-written binding of
bind_text() which does more flexible text conversion, along
the lines of sqlite3_prepare_v3(). The slightly problematic
part is the final argument (text destructor). */
],
//["sqlite3_busy_handler","int", "sqlite3*", "*", "*"],
// ^^^^ TODO: custom binding which auto-converts JS function arg
// to a WASM function, noting that calling it multiple times
// would introduce a leak.
["sqlite3_busy_timeout","int", "sqlite3*", "int"],
["sqlite3_close_v2", "int", "sqlite3*"],
["sqlite3_changes", "int", "sqlite3*"],
["sqlite3_clear_bindings","int", "sqlite3_stmt*"],
["sqlite3_collation_needed", "int", "sqlite3*", "*", "*"/*=>v(ppis)*/],
["sqlite3_column_blob","*", "sqlite3_stmt*", "int"],
["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"],
["sqlite3_column_count", "int", "sqlite3_stmt*"],
["sqlite3_column_double","f64", "sqlite3_stmt*", "int"],
["sqlite3_column_int","int", "sqlite3_stmt*", "int"],
["sqlite3_column_name","string", "sqlite3_stmt*", "int"],
["sqlite3_column_text","string", "sqlite3_stmt*", "int"],
["sqlite3_column_type","int", "sqlite3_stmt*", "int"],
["sqlite3_column_value","sqlite3_value*", "sqlite3_stmt*", "int"],
["sqlite3_compileoption_get", "string", "int"],
["sqlite3_compileoption_used", "int", "string"],
["sqlite3_complete", "int", "string:flexible"],
/* sqlite3_create_function(), sqlite3_create_function_v2(), and
sqlite3_create_window_function() use hand-written bindings to
simplify handling of their function-type arguments. */
/* sqlite3_create_collation() and sqlite3_create_collation_v2()
use hand-written bindings to simplify passing of the callback
function.
["sqlite3_create_collation", "int",
"sqlite3*", "string", "int",//SQLITE_UTF8 is the only legal value
"*", "*"],
["sqlite3_create_collation_v2", "int",
"sqlite3*", "string", "int",//SQLITE_UTF8 is the only legal value
"*", "*", "*"],
*/
["sqlite3_data_count", "int", "sqlite3_stmt*"],
["sqlite3_db_filename", "string", "sqlite3*", "string"],
["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"],
["sqlite3_db_name", "string", "sqlite3*", "int"],
["sqlite3_db_status", "int", "sqlite3*", "int", "*", "*", "int"],
["sqlite3_deserialize", "int", "sqlite3*", "string", "*", "i64", "i64", "int"]
/* Careful! Short version: de/serialize() are problematic because they
might use a different allocator than the user for managing the
deserialized block. de/serialize() are ONLY safe to use with
sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */,
["sqlite3_errcode", "int", "sqlite3*"],
["sqlite3_errmsg", "string", "sqlite3*"],
["sqlite3_error_offset", "int", "sqlite3*"],
["sqlite3_errstr", "string", "int"],
/*["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**"
Handled seperately to perform translation of the callback
into a WASM-usable one. ],*/
["sqlite3_expanded_sql", "string", "sqlite3_stmt*"],
["sqlite3_extended_errcode", "int", "sqlite3*"],
["sqlite3_extended_result_codes", "int", "sqlite3*", "int"],
["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"],
["sqlite3_finalize", "int", "sqlite3_stmt*"],
["sqlite3_free", undefined,"*"],
["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"],
["sqlite3_initialize", undefined],
/*["sqlite3_interrupt", undefined, "sqlite3*"
^^^ we cannot actually currently support this because JS is
single-threaded and we don't have a portable way to access a DB
from 2 SharedWorkers concurrently. ],*/
["sqlite3_keyword_count", "int"],
["sqlite3_keyword_name", "int", ["int", "**", "*"]],
["sqlite3_keyword_check", "int", ["string", "int"]],
["sqlite3_libversion", "string"],
["sqlite3_libversion_number", "int"],
["sqlite3_limit", "int", ["sqlite3*", "int", "int"]],
["sqlite3_malloc", "*","int"],
["sqlite3_open", "int", "string", "*"],
["sqlite3_open_v2", "int", "string", "*", "int", "string"],
/* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled
separately due to us requiring two different sets of semantics
for those, depending on how their SQL argument is provided. */
/* sqlite3_randomness() uses a hand-written wrapper to extend
the range of supported argument types. */
[
"sqlite3_progress_handler", undefined, [
"sqlite3*", "int", new wasm.xWrap.FuncPtrAdapter({
name: 'xProgressHandler',
signature: 'i(p)',
bindScope: 'context',
contextKey: (argIndex,argv)=>'sqlite3@'+argv[0]
}), "*"
]
],
["sqlite3_realloc", "*","*","int"],
["sqlite3_reset", "int", "sqlite3_stmt*"],
["sqlite3_result_blob", undefined, "sqlite3_context*", "*", "int", "*"],
["sqlite3_result_double", undefined, "sqlite3_context*", "f64"],
["sqlite3_result_error", undefined, "sqlite3_context*", "string", "int"],
["sqlite3_result_error_code", undefined, "sqlite3_context*", "int"],
["sqlite3_result_error_nomem", undefined, "sqlite3_context*"],
["sqlite3_result_error_toobig", undefined, "sqlite3_context*"],
["sqlite3_result_int", undefined, "sqlite3_context*", "int"],
["sqlite3_result_null", undefined, "sqlite3_context*"],
["sqlite3_result_pointer", undefined,
"sqlite3_context*", "*", "string:static", "*"],
["sqlite3_result_subtype", undefined, "sqlite3_value*", "int"],
["sqlite3_result_text", undefined, "sqlite3_context*", "string", "int", "*"],
["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"],
["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"],
["sqlite3_set_auxdata", undefined, "sqlite3_context*", "int", "*", "*"/* => v(*) */],
["sqlite3_shutdown", undefined],
["sqlite3_sourceid", "string"],
["sqlite3_sql", "string", "sqlite3_stmt*"],
["sqlite3_status", "int", "int", "*", "*", "int"],
["sqlite3_step", "int", "sqlite3_stmt*"],
["sqlite3_stmt_isexplain", "int", ["sqlite3_stmt*"]],
["sqlite3_stmt_readonly", "int", ["sqlite3_stmt*"]],
["sqlite3_stmt_status", "int", "sqlite3_stmt*", "int", "int"],
["sqlite3_strglob", "int", "string","string"],
["sqlite3_stricmp", "int", "string", "string"],
["sqlite3_strlike", "int", "string", "string","int"],
["sqlite3_strnicmp", "int", "string", "string", "int"],
["sqlite3_table_column_metadata", "int",
"sqlite3*", "string", "string", "string",
"**", "**", "*", "*", "*"],
["sqlite3_total_changes", "int", "sqlite3*"],
["sqlite3_trace_v2", "int", "sqlite3*", "int",
new wasm.xWrap.FuncPtrAdapter({
name: 'sqlite3_trace_v2::callback',
signature: 'i(ippp)',
contextKey: (argIndex, argv)=>'sqlite3@'+argv[0]
}), "*"],
["sqlite3_txn_state", "int", ["sqlite3*","string"]],
/* Note that sqlite3_uri_...() have very specific requirements for
their first C-string arguments, so we cannot perform any value
conversion on those. */
["sqlite3_uri_boolean", "int", "sqlite3_filename", "string", "int"],
["sqlite3_uri_key", "string", "sqlite3_filename", "int"],
["sqlite3_uri_parameter", "string", "sqlite3_filename", "string"],
["sqlite3_user_data","void*", "sqlite3_context*"],
["sqlite3_value_blob", "*", "sqlite3_value*"],
["sqlite3_value_bytes","int", "sqlite3_value*"],
["sqlite3_value_double","f64", "sqlite3_value*"],
["sqlite3_value_dup", "sqlite3_value*", "sqlite3_value*"],
["sqlite3_value_free", undefined, "sqlite3_value*"],
["sqlite3_value_frombind", "int", "sqlite3_value*"],
["sqlite3_value_int","int", "sqlite3_value*"],
["sqlite3_value_nochange", "int", "sqlite3_value*"],
["sqlite3_value_numeric_type", "int", "sqlite3_value*"],
["sqlite3_value_pointer", "*", "sqlite3_value*", "string:static"],
["sqlite3_value_subtype", "int", "sqlite3_value*"],
["sqlite3_value_text", "string", "sqlite3_value*"],
["sqlite3_value_type", "int", "sqlite3_value*"],
["sqlite3_vfs_find", "*", "string"],
["sqlite3_vfs_register", "int", "sqlite3_vfs*", "int"],
["sqlite3_vfs_unregister", "int", "sqlite3_vfs*"]
]/*wasm.bindingSignatures*/;
if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){
/* ^^^ "the problem" is that this is an option feature and the
build-time function-export list does not currently take
optional features into account. */
wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]);
}
/**
Functions which require BigInt (int64) support are separated from
the others because we need to conditionally bind them or apply
dummy impls, depending on the capabilities of the environment.
Note that not all of these functions directly require int64
but are only for use with APIs which require int64. For example,
the vtab-related functions.
*/
wasm.bindingSignatures.int64 = [
["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]],
["sqlite3_changes64","i64", ["sqlite3*"]],
["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]],
["sqlite3_create_module", "int",
["sqlite3*","string","sqlite3_module*","*"]],
["sqlite3_create_module_v2", "int",
["sqlite3*","string","sqlite3_module*","*","*"]],
["sqlite3_declare_vtab", "int", ["sqlite3*", "string:flexible"]],
["sqlite3_drop_modules", "int", ["sqlite3*", "**"]],
["sqlite3_last_insert_rowid", "i64", ["sqlite3*"]],
["sqlite3_malloc64", "*","i64"],
["sqlite3_msize", "i64", "*"],
["sqlite3_overload_function", "int", ["sqlite3*","string","int"]],
["sqlite3_realloc64", "*","*", "i64"],
["sqlite3_result_int64", undefined, "*", "i64"],
["sqlite3_result_zeroblob64", "int", "*", "i64"],
["sqlite3_set_last_insert_rowid", undefined, ["sqlite3*", "i64"]],
["sqlite3_status64", "int", "int", "*", "*", "int"],
["sqlite3_total_changes64", "i64", ["sqlite3*"]],
["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]],
["sqlite3_value_int64","i64", "sqlite3_value*"],
["sqlite3_vtab_collation","string","sqlite3_index_info*","int"],
["sqlite3_vtab_distinct","int", "sqlite3_index_info*"],
["sqlite3_vtab_in","int", "sqlite3_index_info*", "int", "int"],
["sqlite3_vtab_in_first", "int", "sqlite3_value*", "**"],
["sqlite3_vtab_in_next", "int", "sqlite3_value*", "**"],
/*["sqlite3_vtab_config" is variadic and requires a hand-written
proxy.] */
["sqlite3_vtab_nochange","int", "sqlite3_context*"],
["sqlite3_vtab_on_conflict","int", "sqlite3*"],
["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"]
];
/**
Functions which are intended solely for API-internal use by the
WASM components, not client code. These get installed into
sqlite3.wasm. Some of them get exposed to clients via variants
named sqlite3_js_...().
*/
wasm.bindingSignatures.wasm = [
["sqlite3_wasm_db_reset", "int", "sqlite3*"],
["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"],
["sqlite3_wasm_vfs_create_file", "int",
"sqlite3_vfs*","string","*", "int"],
["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"]
];
/** /**
Install JS<->C struct bindings for the non-opaque struct types we Install JS<->C struct bindings for the non-opaque struct types we
need... */ need... */
@@ -31,52 +282,77 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
heap: 0 ? wasm.memory : wasm.heap8u, heap: 0 ? wasm.memory : wasm.heap8u,
alloc: wasm.alloc, alloc: wasm.alloc,
dealloc: wasm.dealloc, dealloc: wasm.dealloc,
functionTable: wasm.functionTable,
bigIntEnabled: wasm.bigIntEnabled, bigIntEnabled: wasm.bigIntEnabled,
memberPrefix: '$' memberPrefix: /* Never change this: this prefix is baked into any
amount of code and client-facing docs. */ '$'
}); });
delete self.Jaccwabyt; delete self.Jaccwabyt;
if(0){
/* "The problem" is that the following isn't even remotely
type-safe. OTOH, nothing about WASM pointers is. */
const argPointer = wasm.xWrap.argAdapter('*');
wasm.xWrap.argAdapter('StructType', (v)=>{
if(v && v.constructor && v instanceof StructBinder.StructType){
v = v.pointer;
}
return wasm.isPtr(v)
? argPointer(v)
: toss("Invalid (object) type for StructType-type argument.");
});
}
{/* Convert Arrays and certain TypedArrays to strings for {/* Convert Arrays and certain TypedArrays to strings for
'flexible-string'-type arguments */ 'string:flexible'-type arguments */
const xString = wasm.xWrap.argAdapter('string'); const xString = wasm.xWrap.argAdapter('string');
wasm.xWrap.argAdapter( wasm.xWrap.argAdapter(
'flexible-string', (v)=>xString(util.flexibleString(v)) 'string:flexible', (v)=>xString(util.flexibleString(v))
); );
}
/**
The 'string:static' argument adapter treats its argument as
either...
- WASM pointer: assumed to be a long-lived C-string which gets
returned as-is.
- Anything else: gets coerced to a JS string for use as a map
key. If a matching entry is found (as described next), it is
returned, else wasm.allocCString() is used to create a a new
string, map its pointer to (''+v) for the remainder of the
application's life, and returns that pointer value for this
call and all future calls which are passed a
string-equivalent argument.
Use case: sqlite3_bind_pointer() and sqlite3_result_pointer()
call for "a static string and preferably a string
literal". This converter is used to ensure that the string
value seen by those functions is long-lived and behaves as they
need it to.
*/
wasm.xWrap.argAdapter(
'string:static',
function(v){
if(wasm.isPtr(v)) return v;
v = ''+v;
let rc = this[v];
return rc || (rc = this[v] = wasm.allocCString(v));
}.bind(Object.create(null))
);
}/* special-case string-type argument conversions */
if(1){// WhWasmUtil.xWrap() bindings... if(1){// WhWasmUtil.xWrap() bindings...
/** /**
Add some descriptive xWrap() aliases for '*' intended to (A) Add some descriptive xWrap() aliases for '*' intended to (A)
initially improve readability/correctness of capi.signatures initially improve readability/correctness of capi.signatures
and (B) eventually perhaps provide automatic conversion from and (B) provide automatic conversion from higher-level
higher-level representations, e.g. capi.sqlite3_vfs to representations, e.g. capi.sqlite3_vfs to `sqlite3_vfs*` via
`sqlite3_vfs*` via capi.sqlite3_vfs.pointer. capi.sqlite3_vfs.pointer.
*/ */
const aPtr = wasm.xWrap.argAdapter('*'); const aPtr = wasm.xWrap.argAdapter('*');
const nilType = function(){};
wasm.xWrap.argAdapter('sqlite3_filename', aPtr) wasm.xWrap.argAdapter('sqlite3_filename', aPtr)
('sqlite3_stmt*', aPtr)
('sqlite3_context*', aPtr) ('sqlite3_context*', aPtr)
('sqlite3_value*', aPtr) ('sqlite3_value*', aPtr)
('void*', aPtr) ('void*', aPtr)
('sqlite3*', (v)=>{ ('sqlite3_stmt*', (v)=>
if(sqlite3.oo1 && v instanceof sqlite3.oo1.DB) v = v.pointer; aPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType))
return aPtr(v); ? v.pointer : v))
}) ('sqlite3*', (v)=>
aPtr((v instanceof (sqlite3?.oo1?.DB || nilType))
? v.pointer : v))
('sqlite3_index_info*', (v)=>
aPtr((v instanceof (capi.sqlite3_index_info || nilType))
? v.pointer : v))
('sqlite3_module*', (v)=>
aPtr((v instanceof (capi.sqlite3_module || nilType))
? v.pointer : v))
/** /**
`sqlite3_vfs*`: `sqlite3_vfs*`:
@@ -87,21 +363,23 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
*/ */
('sqlite3_vfs*', (v)=>{ ('sqlite3_vfs*', (v)=>{
if('string'===typeof v){ if('string'===typeof v){
const x = capi.sqlite3_vfs_find(v);
/* A NULL sqlite3_vfs pointer will be treated as the default /* A NULL sqlite3_vfs pointer will be treated as the default
VFS in many contexts. We specifically do not want that VFS in many contexts. We specifically do not want that
behavior here. */ behavior here. */
if(!x) sqlite3.SQLite3Error.toss("Unknown sqlite3_vfs name:",v); return capi.sqlite3_vfs_find(v)
return x; || sqlite3.SQLite3Error.toss("Unknown sqlite3_vfs name:",v);
}else if(v instanceof sqlite3.capi.sqlite3_vfs) v = v.pointer; }
return aPtr(v); return aPtr((v instanceof (capi.sqlite3_vfs || nilType))
? v.pointer : v);
}); });
wasm.xWrap.resultAdapter('sqlite3*', aPtr) const rPtr = wasm.xWrap.resultAdapter('*');
('sqlite3_context*', aPtr) wasm.xWrap.resultAdapter('sqlite3*', rPtr)
('sqlite3_stmt*', aPtr) ('sqlite3_context*', rPtr)
('sqlite3_vfs*', aPtr) ('sqlite3_stmt*', rPtr)
('void*', aPtr); ('sqlite3_value*', rPtr)
('sqlite3_vfs*', rPtr)
('void*', rPtr);
/** /**
Populate api object with sqlite3_...() by binding the "raw" wasm Populate api object with sqlite3_...() by binding the "raw" wasm
@@ -127,35 +405,48 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
: fI64Disabled(e[0]); : fI64Disabled(e[0]);
} }
/* There's no(?) need to expose bindingSignatures to clients, /* There's no need to expose bindingSignatures to clients,
implicitly making it part of the public interface. */ implicitly making it part of the public interface. */
delete wasm.bindingSignatures; delete wasm.bindingSignatures;
if(wasm.exports.sqlite3_wasm_db_error){ if(wasm.exports.sqlite3_wasm_db_error){
util.sqlite3_wasm_db_error = wasm.xWrap( const __db_err = wasm.xWrap(
'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string' 'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string'
); );
/**
Sets the given db's error state. Accepts:
- (sqlite3*, int code, string msg)
- (sqlite3*, Error e [,string msg = ''+e])
If passed a WasmAllocError, the message is ingored and the
result code is SQLITE_NOMEM. If passed any other Error type,
the result code defaults to SQLITE_ERROR unless the Error
object has a resultCode property, in which case that is used
(e.g. SQLite3Error has that). If passed a non-WasmAllocError
exception, the message string defaults to theError.message.
Returns the resulting code. Pass (pDb,0,0) to clear the error
state.
*/
util.sqlite3_wasm_db_error = function(pDb, resultCode, message){
if(resultCode instanceof sqlite3.WasmAllocError){
resultCode = capi.SQLITE_NOMEM;
message = 0 /*avoid allocating message string*/;
}else if(resultCode instanceof Error){
message = message || ''+resultCode;
resultCode = (resultCode.resultCode || capi.SQLITE_ERROR);
}
return __db_err(pDb, resultCode, message);
};
}else{ }else{
util.sqlite3_wasm_db_error = function(pDb,errCode,msg){ util.sqlite3_wasm_db_error = function(pDb,errCode,msg){
console.warn("sqlite3_wasm_db_error() is not exported.",arguments); console.warn("sqlite3_wasm_db_error() is not exported.",arguments);
return errCode; return errCode;
}; };
} }
}/*xWrap() bindings*/; }/*xWrap() bindings*/;
/**
When registering a VFS and its related components it may be
necessary to ensure that JS keeps a reference to them to keep
them from getting garbage collected. Simply pass each such value
to this function and a reference will be held to it for the life
of the app.
*/
capi.sqlite3_vfs_register.addReference = function f(...args){
if(!f._) f._ = [];
f._.push(...args);
};
/** /**
Internal helper to assist in validating call argument counts in Internal helper to assist in validating call argument counts in
the hand-written sqlite3_xyz() wrappers. We do this only for the hand-written sqlite3_xyz() wrappers. We do this only for
@@ -167,34 +458,103 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
(1===n?"":'s')+"."); (1===n?"":'s')+".");
}; };
/** if(1){/* Bindings for sqlite3_create_collation() */
Helper for flexible-string conversions which require a
byte-length counterpart argument. Passed a value and its const __collationContextKey = (argIndex,argv)=>{
ostensible length, this function returns [V,N], where V return 'argv['+argIndex+']:sqlite3@'+argv[0]+
is either v or a transformed copy of v and N is either n, ':'+((/*THIS IS WRONG. We can't sensibly use a converted-to-C-string
-1, or the byte length of v (if it's a byte array). address here and don't have access to the JS string (IF ANY)
*/ which the user passed in.*/
const __flexiString = function(v,n){ ''+argv[1]
if('string'===typeof v){ ).toLowerCase());
n = -1; };
}else if(util.isSQLableTypedArray(v)){ const __ccv2 = wasm.xWrap(
n = v.byteLength; 'sqlite3_create_collation_v2', 'int',
v = util.typedArrayToString(v); 'sqlite3*','string','int','*','*','*'
}else if(Array.isArray(v)){ /* int(*xCompare)(void*,int,const void*,int,const void*) */
v = v.join(""); /* void(*xDestroy(void*) */
n = -1; );
if(0){
// Problem: we cannot, due to xWrap() arg-passing limitations,
// currently easily/efficiently get a per-collation distinct
// key for purposes of creating distinct FuncPtrAdapter contexts.
new wasm.xWrap.FuncPtrAdapter({
/* int(*xCompare)(void*,int,const void*,int,const void*) */
name: 'xCompare',
signature: 'i(pipip)',
bindScope: 'context',
contextKey: __collationContextKey
}),
new wasm.xWrap.FuncPtrAdapter({
/* void(*xDestroy(void*) */
name: 'xDestroy',
signature: 'v(p)',
bindScope: 'context',
contextKey: __collationContextKey
})
} }
return [v, n];
}; /**
Works exactly like C's sqlite3_create_collation_v2() except that:
1) It accepts JS functions for its function-pointer arguments,
for which it will install WASM-bound proxies. The bindings
are "permanent," in that they will stay in the WASM environment
until it shuts down unless the client somehow finds and removes
them.
2) It returns capi.SQLITE_FORMAT if the 3rd argument is not
capi.SQLITE_UTF8. No other encodings are supported.
Returns 0 on success, non-0 on error, in which case the error
state of pDb (of type `sqlite3*` or argument-convertible to it)
may contain more information.
*/
capi.sqlite3_create_collation_v2 = function(pDb,zName,eTextRep,pArg,xCompare,xDestroy){
if(6!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_create_collation_v2', 6);
else if(capi.SQLITE_UTF8!==eTextRep){
return util.sqlite3_wasm_db_error(
pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding."
);
}
let rc, pfCompare, pfDestroy;
try{
if(xCompare instanceof Function){
pfCompare = wasm.installFunction(xCompare, 'i(pipip)');
}
if(xDestroy instanceof Function){
pfDestroy = wasm.installFunction(xDestroy, 'v(p)');
}
rc = __ccv2(pDb, zName, eTextRep, pArg,
pfCompare || xCompare, pfDestroy || xDestroy);
}catch(e){
if(pfCompare) wasm.uninstallFunction(pfCompare);
if(pfDestroy) wasm.uninstallFunction(pfDestroy);
rc = util.sqlite3_wasm_db_error(pDb, e);
}
return rc;
};
capi.sqlite3_create_collation = (pDb,zName,eTextRep,pArg,xCompare)=>{
return (5===arguments.length)
? capi.sqlite3_create_collation_v2(pDb,zName,eTextRep,pArg,xCompare,0)
: __dbArgcMismatch(pDb, 'sqlite3_create_collation', 5);
}
}/*sqlite3_create_collation() and friends*/
if(1){/* Special-case handling of sqlite3_exec() */ if(1){/* Special-case handling of sqlite3_exec() */
const __exec = wasm.xWrap("sqlite3_exec", "int", const __exec = wasm.xWrap("sqlite3_exec", "int",
["sqlite3*", "flexible-string", "*", "*", "**"]); ["sqlite3*", "string:flexible",
new wasm.xWrap.FuncPtrAdapter({
signature: 'i(pipp)',
bindScope: 'transient'
}), "*", "**"]);
/* Documented in the api object's initializer. */ /* Documented in the api object's initializer. */
capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){ capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){
if(f.length!==arguments.length){ if(f.length!==arguments.length){
return __dbArgcMismatch(pDb,"sqlite3_exec",f.length); return __dbArgcMismatch(pDb,"sqlite3_exec",f.length);
}else if('function' !== typeof callback){ }else if(!(callback instanceof Function)){
return __exec(pDb, sql, callback, pVoid, pErrMsg); return __exec(pDb, sql, callback, pVoid, pErrMsg);
} }
/* Wrap the callback in a WASM-bound function and convert the callback's /* Wrap the callback in a WASM-bound function and convert the callback's
@@ -204,8 +564,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try { try {
let aVals = [], aNames = [], i = 0, offset = 0; let aVals = [], aNames = [], i = 0, offset = 0;
for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){ for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){
aVals.push( wasm.cstringToJs(wasm.getPtrValue(pColVals + offset)) ); aVals.push( wasm.cstrToJs(wasm.peekPtr(pColVals + offset)) );
aNames.push( wasm.cstringToJs(wasm.getPtrValue(pColNames + offset)) ); aNames.push( wasm.cstrToJs(wasm.peekPtr(pColNames + offset)) );
} }
rc = callback(pVoid, nCols, aVals, aNames) | 0; rc = callback(pVoid, nCols, aVals, aNames) | 0;
/* The first 2 args of the callback are useless for JS but /* The first 2 args of the callback are useless for JS but
@@ -219,15 +579,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
} }
return rc; return rc;
}; };
let pFunc, rc; let rc;
try{ try{
pFunc = wasm.installFunction("ipipp", cbwrap); rc = __exec(pDb, sql, cbwrap, pVoid, pErrMsg);
rc = __exec(pDb, sql, pFunc, pVoid, pErrMsg);
}catch(e){ }catch(e){
rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR,
"Error running exec(): "+e.message); "Error running exec(): "+e);
}finally{
if(pFunc) wasm.uninstallFunction(pFunc);
} }
return rc; return rc;
}; };
@@ -249,132 +606,31 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
"*"/*xInverse*/, "*"/*xDestroy*/] "*"/*xInverse*/, "*"/*xDestroy*/]
); );
const __udfSetResult = function(pCtx, val){
//console.warn("udfSetResult",typeof val, val);
switch(typeof val) {
case 'undefined':
/* Assume that the client already called sqlite3_result_xxx(). */
break;
case 'boolean':
capi.sqlite3_result_int(pCtx, val ? 1 : 0);
break;
case 'bigint':
if(wasm.bigIntEnabled){
if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val);
else toss3("BigInt value",val.toString(),"is too BigInt for int64.");
}else if(util.bigIntFits32(val)){
capi.sqlite3_result_int(pCtx, Number(val));
}else if(util.bigIntFitsDouble(val)){
capi.sqlite3_result_double(pCtx, Number(val));
}else{
toss3("BigInt value",val.toString(),"is too BigInt.");
}
break;
case 'number': {
(util.isInt32(val)
? capi.sqlite3_result_int
: capi.sqlite3_result_double)(pCtx, val);
break;
}
case 'string':
capi.sqlite3_result_text(pCtx, val, -1, capi.SQLITE_TRANSIENT);
break;
case 'object':
if(null===val/*yes, typeof null === 'object'*/) {
capi.sqlite3_result_null(pCtx);
break;
}else if(util.isBindableTypedArray(val)){
const pBlob = wasm.allocFromTypedArray(val);
capi.sqlite3_result_blob(
pCtx, pBlob, val.byteLength,
wasm.exports[sqlite3.config.deallocExportName]
);
break;
}
// else fall through
default:
toss3("Don't not how to handle this UDF result value:",(typeof val), val);
};
}/*__udfSetResult()*/;
const __udfConvertArgs = function(argc, pArgv){
let i, pVal, valType, arg;
const tgt = [];
for(i = 0; i < argc; ++i){
pVal = wasm.getPtrValue(pArgv + (wasm.ptrSizeof * i));
/**
Curiously: despite ostensibly requiring 8-byte
alignment, the pArgv array is parcelled into chunks of
4 bytes (1 pointer each). The values those point to
have 8-byte alignment but the individual argv entries
do not.
*/
valType = capi.sqlite3_value_type(pVal);
switch(valType){
case capi.SQLITE_INTEGER:
if(wasm.bigIntEnabled){
arg = capi.sqlite3_value_int64(pVal);
if(util.bigIntFitsDouble(arg)) arg = Number(arg);
}
else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/;
break;
case capi.SQLITE_FLOAT:
arg = capi.sqlite3_value_double(pVal);
break;
case capi.SQLITE_TEXT:
arg = capi.sqlite3_value_text(pVal);
break;
case capi.SQLITE_BLOB:{
const n = capi.sqlite3_value_bytes(pVal);
const pBlob = capi.sqlite3_value_blob(pVal);
if(n && !pBlob) sqlite3.WasmAllocError.toss(
"Cannot allocate memory for blob argument of",n,"byte(s)"
);
arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null;
break;
}
case capi.SQLITE_NULL:
arg = null; break;
default:
toss3("Unhandled sqlite3_value_type()",valType,
"is possibly indicative of incorrect",
"pointer size assumption.");
}
tgt.push(arg);
}
return tgt;
}/*__udfConvertArgs()*/;
const __udfSetError = (pCtx, e)=>{
if(e instanceof sqlite3.WasmAllocError){
capi.sqlite3_result_error_nomem(pCtx);
}else{
const msg = ('string'===typeof e) ? e : e.message;
capi.sqlite3_result_error(pCtx, msg, -1);
}
};
const __xFunc = function(callback){ const __xFunc = function(callback){
return function(pCtx, argc, pArgv){ return function(pCtx, argc, pArgv){
try{ __udfSetResult(pCtx, callback(pCtx, ...__udfConvertArgs(argc, pArgv))) } try{
catch(e){ capi.sqlite3_result_js(
pCtx,
callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv))
);
}catch(e){
//console.error('xFunc() caught:',e); //console.error('xFunc() caught:',e);
__udfSetError(pCtx, e); capi.sqlite3_result_error_js(pCtx, e);
} }
}; };
}; };
const __xInverseAndStep = function(callback){ const __xInverseAndStep = function(callback){
return function(pCtx, argc, pArgv){ return function(pCtx, argc, pArgv){
try{ callback(pCtx, ...__udfConvertArgs(argc, pArgv)) } try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) }
catch(e){ __udfSetError(pCtx, e) } catch(e){ capi.sqlite3_result_error_js(pCtx, e) }
}; };
}; };
const __xFinalAndValue = function(callback){ const __xFinalAndValue = function(callback){
return function(pCtx){ return function(pCtx){
try{ __udfSetResult(pCtx, callback(pCtx)) } try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) }
catch(e){ __udfSetError(pCtx, e) } catch(e){ capi.sqlite3_result_error_js(pCtx, e) }
}; };
}; };
@@ -480,66 +736,53 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return rc; return rc;
}; };
/** /**
A helper for UDFs implemented in JS and bound to WASM by the A _deprecated_ alias for capi.sqlite3_result_js() which
client. Given a JS value, udfSetResult(pCtx,X) calls one of the predates the addition of that function in the public API.
sqlite3_result_xyz(pCtx,...) routines, depending on X's data
type:
- `null`: sqlite3_result_null()
- `boolean`: sqlite3_result_int()
- `number`: sqlite3_result_int() or sqlite3_result_double()
- `string`: sqlite3_result_text()
- Uint8Array or Int8Array: sqlite3_result_blob()
- `undefined`: indicates that the UDF called one of the
`sqlite3_result_xyz()` routines on its own, making this
function a no-op. Results are _undefined_ if this function is
passed the `undefined` value but did _not_ call one of the
`sqlite3_result_xyz()` routines.
Anything else triggers sqlite3_result_error().
*/ */
capi.sqlite3_create_function_v2.udfSetResult = capi.sqlite3_create_function_v2.udfSetResult =
capi.sqlite3_create_function.udfSetResult = capi.sqlite3_create_function.udfSetResult =
capi.sqlite3_create_window_function.udfSetResult = __udfSetResult; capi.sqlite3_create_window_function.udfSetResult = capi.sqlite3_result_js;
/** /**
A helper for UDFs implemented in JS and bound to WASM by the A _deprecated_ alias for capi.sqlite3_values_to_js() which
client. When passed the predates the addition of that function in the public API.
(argc,argv) values from the UDF-related functions which receive
them (xFunc, xStep, xInverse), it creates a JS array
representing those arguments, converting each to JS in a manner
appropriate to its data type: numeric, text, blob
(Uint8Array), or null.
Results are undefined if it's passed anything other than those
two arguments from those specific contexts.
Thus an argc of 4 will result in a length-4 array containing
the converted values from the corresponding argv.
The conversion will throw only on allocation error or an internal
error.
*/ */
capi.sqlite3_create_function_v2.udfConvertArgs = capi.sqlite3_create_function_v2.udfConvertArgs =
capi.sqlite3_create_function.udfConvertArgs = capi.sqlite3_create_function.udfConvertArgs =
capi.sqlite3_create_window_function.udfConvertArgs = __udfConvertArgs; capi.sqlite3_create_window_function.udfConvertArgs = capi.sqlite3_values_to_js;
/** /**
A helper for UDFs implemented in JS and bound to WASM by the A _deprecated_ alias for capi.sqlite3_result_error_js() which
client. It expects to be a passed `(sqlite3_context*, Error)` predates the addition of that function in the public API.
(an exception object or message string). And it sets the
current UDF's result to sqlite3_result_error_nomem() or
sqlite3_result_error(), depending on whether the 2nd argument
is a sqlite3.WasmAllocError object or not.
*/ */
capi.sqlite3_create_function_v2.udfSetError = capi.sqlite3_create_function_v2.udfSetError =
capi.sqlite3_create_function.udfSetError = capi.sqlite3_create_function.udfSetError =
capi.sqlite3_create_window_function.udfSetError = __udfSetError; capi.sqlite3_create_window_function.udfSetError = capi.sqlite3_result_error_js;
}/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/; }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/;
if(1){/* Special-case handling of sqlite3_prepare_v2() and if(1){/* Special-case handling of sqlite3_prepare_v2() and
sqlite3_prepare_v3() */ sqlite3_prepare_v3() */
/**
Helper for string:flexible conversions which require a
byte-length counterpart argument. Passed a value and its
ostensible length, this function returns [V,N], where V
is either v or a transformed copy of v and N is either n,
-1, or the byte length of v (if it's a byte array).
*/
const __flexiString = (v,n)=>{
if('string'===typeof v){
n = -1;
}else if(util.isSQLableTypedArray(v)){
n = v.byteLength;
v = util.typedArrayToString(v);
}else if(Array.isArray(v)){
v = v.join("");
n = -1;
}
return [v, n];
};
/** /**
Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). Scope-local holder of the two impls of sqlite3_prepare_v2/v3().
*/ */
@@ -570,7 +813,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
"int", ["sqlite3*", "*", "int", "int", "int", ["sqlite3*", "*", "int", "int",
"**", "**"]); "**", "**"]);
/* Documented in the api object's initializer. */ /* Documented in the capi object's initializer. */
capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){
if(f.length!==arguments.length){ if(f.length!==arguments.length){
return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length); return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length);
@@ -587,7 +830,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
} }
}; };
/* Documented in the api object's initializer. */ /* Documented in the capi object's initializer. */
capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){ capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){
return (f.length===arguments.length) return (f.length===arguments.length)
? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail)
@@ -601,15 +844,22 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", toss("Maintenance required: increase sqlite3_wasm_enum_json()'s",
"static buffer size!"); "static buffer size!");
} }
wasm.ctype = JSON.parse(wasm.cstringToJs(cJson)); wasm.ctype = JSON.parse(wasm.cstrToJs(cJson));
//console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); //console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
for(const t of ['access', 'blobFinalizers', 'dataTypes', const defineGroups = ['access', 'authorizer',
'encodings', 'fcntl', 'flock', 'ioCap', 'blobFinalizers', 'dataTypes',
'limits', 'dbConfig', 'dbStatus',
'openFlags', 'prepareFlags', 'resultCodes', 'encodings', 'fcntl', 'flock', 'ioCap',
'serialize', 'syncFlags', 'trace', 'udfFlags', 'limits', 'openFlags',
'version' 'prepareFlags', 'resultCodes',
]){ 'serialize', 'sqlite3Status',
'stmtStatus', 'syncFlags',
'trace', 'txnState', 'udfFlags',
'version' ];
if(wasm.bigIntEnabled){
defineGroups.push('vtab');
}
for(const t of defineGroups){
for(const e of Object.entries(wasm.ctype[t])){ for(const e of Object.entries(wasm.ctype[t])){
// ^^^ [k,v] there triggers a buggy code transformation via // ^^^ [k,v] there triggers a buggy code transformation via
// one of the Emscripten-driven optimizers. // one of the Emscripten-driven optimizers.
@@ -629,19 +879,37 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc];
/* Bind all registered C-side structs... */ /* Bind all registered C-side structs... */
const notThese = Object.assign(Object.create(null),{ const notThese = Object.assign(Object.create(null),{
// Structs NOT to register // For each struct to NOT register, map its name to true:
WasmTestStruct: true WasmTestStruct: true,
/* We unregister the kvvfs VFS from Worker threads below. */
sqlite3_kvvfs_methods: !util.isUIThread(),
/* sqlite3_index_info and friends require int64: */
sqlite3_index_info: !wasm.bigIntEnabled,
sqlite3_index_constraint: !wasm.bigIntEnabled,
sqlite3_index_orderby: !wasm.bigIntEnabled,
sqlite3_index_constraint_usage: !wasm.bigIntEnabled
}); });
if(!util.isUIThread()){
/* We remove the kvvfs VFS from Worker threads below. */
notThese.sqlite3_kvvfs_methods = true;
}
for(const s of wasm.ctype.structs){ for(const s of wasm.ctype.structs){
if(!notThese[s.name]){ if(!notThese[s.name]){
capi[s.name] = sqlite3.StructBinder(s); capi[s.name] = sqlite3.StructBinder(s);
} }
} }
}/*end C constant imports*/ if(capi.sqlite3_index_info){
/* Move these inner structs into sqlite3_index_info. Binding
** them to WASM requires that we create global-scope structs to
** model them with, but those are no longer needed after we've
** passed them to StructBinder. */
for(const k of ['sqlite3_index_constraint',
'sqlite3_index_orderby',
'sqlite3_index_constraint_usage']){
capi.sqlite3_index_info[k] = capi[k];
delete capi[k];
}
capi.sqlite3_vtab_config =
(pDb, op, arg=0)=>wasm.exports.sqlite3_wasm_vtab_config(
wasm.xWrap.argAdapter('sqlite3*')(pDb), op, arg);
}/* end vtab-related setup */
}/*end C constant and struct imports*/
const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); const pKvvfs = capi.sqlite3_vfs_find("kvvfs");
if( pKvvfs ){/* kvvfs-specific glue */ if( pKvvfs ){/* kvvfs-specific glue */
@@ -652,11 +920,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
delete capi.sqlite3_kvvfs_methods; delete capi.sqlite3_kvvfs_methods;
const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack, const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack,
pstack = wasm.pstack, pstack = wasm.pstack;
pAllocRaw = wasm.exports.sqlite3_wasm_pstack_alloc;
const kvvfsStorage = (zClass)=> const kvvfsStorage = (zClass)=>
((115/*=='s'*/===wasm.getMemValue(zClass)) ((115/*=='s'*/===wasm.peek(zClass))
? sessionStorage : localStorage); ? sessionStorage : localStorage);
/** /**
@@ -672,7 +939,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try { try {
const zXKey = kvvfsMakeKey(zClass,zKey); const zXKey = kvvfsMakeKey(zClass,zKey);
if(!zXKey) return -3/*OOM*/; if(!zXKey) return -3/*OOM*/;
const jKey = wasm.cstringToJs(zXKey); const jKey = wasm.cstrToJs(zXKey);
const jV = kvvfsStorage(zClass).getItem(jKey); const jV = kvvfsStorage(zClass).getItem(jKey);
if(!jV) return -1; if(!jV) return -1;
const nV = jV.length /* Note that we are relying 100% on v being const nV = jV.length /* Note that we are relying 100% on v being
@@ -680,13 +947,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
C-string's byte length. */; C-string's byte length. */;
if(nBuf<=0) return nV; if(nBuf<=0) return nV;
else if(1===nBuf){ else if(1===nBuf){
wasm.setMemValue(zBuf, 0); wasm.poke(zBuf, 0);
return nV; return nV;
} }
const zV = wasm.scopedAllocCString(jV); const zV = wasm.scopedAllocCString(jV);
if(nBuf > nV + 1) nBuf = nV + 1; if(nBuf > nV + 1) nBuf = nV + 1;
wasm.heap8u().copyWithin(zBuf, zV, zV + nBuf - 1); wasm.heap8u().copyWithin(zBuf, zV, zV + nBuf - 1);
wasm.setMemValue(zBuf + nBuf - 1, 0); wasm.poke(zBuf + nBuf - 1, 0);
return nBuf - 1; return nBuf - 1;
}catch(e){ }catch(e){
console.error("kvstorageRead()",e); console.error("kvstorageRead()",e);
@@ -701,8 +968,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try { try {
const zXKey = kvvfsMakeKey(zClass,zKey); const zXKey = kvvfsMakeKey(zClass,zKey);
if(!zXKey) return 1/*OOM*/; if(!zXKey) return 1/*OOM*/;
const jKey = wasm.cstringToJs(zXKey); const jKey = wasm.cstrToJs(zXKey);
kvvfsStorage(zClass).setItem(jKey, wasm.cstringToJs(zData)); kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData));
return 0; return 0;
}catch(e){ }catch(e){
console.error("kvstorageWrite()",e); console.error("kvstorageWrite()",e);
@@ -716,7 +983,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try { try {
const zXKey = kvvfsMakeKey(zClass,zKey); const zXKey = kvvfsMakeKey(zClass,zKey);
if(!zXKey) return 1/*OOM*/; if(!zXKey) return 1/*OOM*/;
kvvfsStorage(zClass).removeItem(wasm.cstringToJs(zXKey)); kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey));
return 0; return 0;
}catch(e){ }catch(e){
console.error("kvstorageDelete()",e); console.error("kvstorageDelete()",e);

View File

@@ -73,7 +73,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(capi.SQLITE_TRACE_STMT===t){ if(capi.SQLITE_TRACE_STMT===t){
// x == SQL, p == sqlite3_stmt* // x == SQL, p == sqlite3_stmt*
console.log("SQL TRACE #"+(++this.counter), console.log("SQL TRACE #"+(++this.counter),
wasm.cstringToJs(x)); wasm.cstrToJs(x));
} }
}.bind({counter: 0})); }.bind({counter: 0}));
@@ -139,7 +139,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
console.error("Invalid DB ctor args",opt,arguments); console.error("Invalid DB ctor args",opt,arguments);
toss3("Invalid arguments for DB constructor."); toss3("Invalid arguments for DB constructor.");
} }
let fnJs = ('number'===typeof fn) ? wasm.cstringToJs(fn) : fn; let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn;
const vfsCheck = ctor._name2vfs[fnJs]; const vfsCheck = ctor._name2vfs[fnJs];
if(vfsCheck){ if(vfsCheck){
vfsName = vfsCheck.vfs; vfsName = vfsCheck.vfs;
@@ -156,7 +156,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try { try {
const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */; const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0); let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0);
pDb = wasm.getPtrValue(pPtr); pDb = wasm.peekPtr(pPtr);
checkSqlite3Rc(pDb, rc); checkSqlite3Rc(pDb, rc);
if(flagsStr.indexOf('t')>=0){ if(flagsStr.indexOf('t')>=0){
capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT, capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT,
@@ -192,13 +192,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/** /**
Sets SQL which should be exec()'d on a DB instance after it is Sets SQL which should be exec()'d on a DB instance after it is
opened with the given VFS pointer. The SQL may be any type opened with the given VFS pointer. The SQL may be any type
supported by the "flexible-string" function argument supported by the "string:flexible" function argument conversion.
conversion. Alternately, the 2nd argument may be a function, in Alternately, the 2nd argument may be a function, in which case it
which case it is called with (theOo1DbObject,sqlite3Namespace) at is called with (theOo1DbObject,sqlite3Namespace) at the end of
the end of the DB() constructor. The function must throw on the DB() constructor. The function must throw on error, in which
error, in which case the db is closed and the exception is case the db is closed and the exception is propagated. This
propagated. This function is intended only for use by DB function is intended only for use by DB subclasses or sqlite3_vfs
subclasses or sqlite3_vfs implementations. implementations.
*/ */
dbCtorHelper.setVfsPostOpenSql = function(pVfs, sql){ dbCtorHelper.setVfsPostOpenSql = function(pVfs, sql){
__vfsPostOpenSql[pVfs] = sql; __vfsPostOpenSql[pVfs] = sql;
@@ -473,6 +473,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return rc; return rc;
}; };
/**
Internal impl of the DB.selectArrays() and
selectObjects() methods.
*/
const __selectAll =
(db, sql, bind, rowMode)=>db.exec({
sql, bind, rowMode, returnValue: 'resultRows'
});
/** /**
Expects to be given a DB instance or an `sqlite3*` pointer (may Expects to be given a DB instance or an `sqlite3*` pointer (may
be null) and an sqlite3 API result code. If the result code is be null) and an sqlite3 API result code. If the result code is
@@ -511,10 +520,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
db is closed but before auxiliary state like this.filename is db is closed but before auxiliary state like this.filename is
cleared. cleared.
Both onclose handlers are passed this object. If this db is not Both onclose handlers are passed this object, with the onclose
opened, neither of the handlers are called. Any exceptions the object as their "this," noting that the db will have been
handlers throw are ignored because "destructors must not closed when onclose.after is called. If this db is not opened
throw." when close() is called, neither of the handlers are called. Any
exceptions the handlers throw are ignored because "destructors
must not throw."
Note that garbage collection of a db handle, if it happens at Note that garbage collection of a db handle, if it happens at
all, will never trigger close(), so onclose handlers are not a all, will never trigger close(), so onclose handlers are not a
@@ -591,7 +602,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
); );
if(pVfs){ if(pVfs){
const v = new capi.sqlite3_vfs(pVfs); const v = new capi.sqlite3_vfs(pVfs);
try{ rc = wasm.cstringToJs(v.$zName) } try{ rc = wasm.cstrToJs(v.$zName) }
finally { v.dispose() } finally { v.dispose() }
} }
return rc; return rc;
@@ -625,7 +636,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try{ try{
ppStmt = wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */; ppStmt = wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */;
DB.checkRc(this, capi.sqlite3_prepare_v2(this.pointer, sql, -1, ppStmt, null)); DB.checkRc(this, capi.sqlite3_prepare_v2(this.pointer, sql, -1, ppStmt, null));
pStmt = wasm.getPtrValue(ppStmt); pStmt = wasm.peekPtr(ppStmt);
} }
finally { finally {
wasm.pstack.restore(stack); wasm.pstack.restore(stack);
@@ -805,8 +816,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const pSqlEnd = pSql + sqlByteLen; const pSqlEnd = pSql + sqlByteLen;
if(isTA) wasm.heap8().set(arg.sql, pSql); if(isTA) wasm.heap8().set(arg.sql, pSql);
else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false); else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false);
wasm.setMemValue(pSql + sqlByteLen, 0/*NUL terminator*/); wasm.poke(pSql + sqlByteLen, 0/*NUL terminator*/);
while(pSql && wasm.getMemValue(pSql, 'i8') while(pSql && wasm.peek(pSql, 'i8')
/* Maintenance reminder:^^^ _must_ be 'i8' or else we /* Maintenance reminder:^^^ _must_ be 'i8' or else we
will very likely cause an endless loop. What that's will very likely cause an endless loop. What that's
doing is checking for a terminating NUL byte. If we doing is checking for a terminating NUL byte. If we
@@ -814,13 +825,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
around the NUL terminator, and get stuck in and around the NUL terminator, and get stuck in and
endless loop at the end of the SQL, endlessly endless loop at the end of the SQL, endlessly
re-preparing an empty statement. */ ){ re-preparing an empty statement. */ ){
wasm.setPtrValue(ppStmt, 0); wasm.pokePtr([ppStmt, pzTail], 0);
wasm.setPtrValue(pzTail, 0);
DB.checkRc(this, capi.sqlite3_prepare_v3( DB.checkRc(this, capi.sqlite3_prepare_v3(
this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail
)); ));
const pStmt = wasm.getPtrValue(ppStmt); const pStmt = wasm.peekPtr(ppStmt);
pSql = wasm.getPtrValue(pzTail); pSql = wasm.peekPtr(pzTail);
sqlByteLen = pSqlEnd - pSql; sqlByteLen = pSqlEnd - pSql;
if(!pStmt) continue; if(!pStmt) continue;
if(Array.isArray(opt.saveSql)){ if(Array.isArray(opt.saveSql)){
@@ -1098,6 +1108,26 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return __selectFirstRow(this, sql, bind, {}); return __selectFirstRow(this, sql, bind, {});
}, },
/**
Runs the given SQL and returns an array of all results, with
each row represented as an array, as per the 'array' `rowMode`
option to `exec()`. An empty result set resolves
to an empty array. The second argument, if any, is treated as
the 'bind' option to a call to exec().
*/
selectArrays: function(sql,bind){
return __selectAll(this, sql, bind, 'array');
},
/**
Works identically to selectArrays() except that each value
in the returned array is an object, as per the 'object' `rowMode`
option to `exec()`.
*/
selectObjects: function(sql,bind){
return __selectAll(this, sql, bind, 'object');
},
/** /**
Returns the number of currently-opened Stmt handles for this db Returns the number of currently-opened Stmt handles for this db
handle, or 0 if this DB instance is closed. handle, or 0 if this DB instance is closed.

View File

@@ -185,28 +185,49 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
/** /**
Constructs this object with a message depending on its arguments: Constructs this object with a message depending on its arguments:
- If it's passed only a single integer argument, it is assumed If its first argument is an integer, it is assumed to be
to be an sqlite3 C API result code. The message becomes the an SQLITE_... result code and it is passed to
result of sqlite3.capi.sqlite3_js_rc_str() or (if that returns sqlite3.capi.sqlite3_js_rc_str() to stringify it.
falsy) a synthesized string which contains that integer.
- If passed 2 arguments and the 2nd is a object, it behaves If called with exactly 2 arguments and the 2nd is an object,
like the Error(string,object) constructor except that the first that object is treated as the 2nd argument to the parent
argument is subject to the is-integer semantics from the constructor.
previous point.
- Else all arguments are concatenated with a space between each The exception's message is created by concatenating its
one, using args.join(' '), to create the error message. arguments with a space between each, except for the
two-args-with-an-objec form and that the first argument will
get coerced to a string, as described above, if it's an
integer.
If passed an integer first argument, the error object's
`resultCode` member will be set to the given integer value,
else it will be set to capi.SQLITE_ERROR.
*/ */
constructor(...args){ constructor(...args){
if(1===args.length && __isInt(args[0])){ let rc;
super(__rcStr(args[0])); if(args.length){
}else if(2===args.length && 'object'===typeof args[1]){ if(__isInt(args[0])){
if(__isInt(args[0])) super(__rcStr(args[0]), args[1]); rc = args[0];
else super(...args); if(1===args.length){
}else{ super(__rcStr(args[0]));
super(args.join(' ')); }else{
const rcStr = __rcStr(rc);
if('object'===typeof args[1]){
super(rcStr,args[1]);
}else{
args[0] = rcStr+':';
super(args.join(' '));
}
}
}else{
if(2===args.length && 'object'===typeof args[1]){
super(...args);
}else{
super(args.join(' '));
}
}
} }
this.resultCode = rc || capi.SQLITE_ERROR;
this.name = 'SQLite3Error'; this.name = 'SQLite3Error';
} }
}; };
@@ -348,13 +369,13 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
/** /**
If v is-a Array, its join("") result is returned. If If v is-a Array, its join("") result is returned. If
isSQLableTypedArray(v) is true then typedArrayToString(v) is isSQLableTypedArray(v) is true then typedArrayToString(v) is
returned. If it looks like a WASM pointer, wasm.cstringToJs(v) is returned. If it looks like a WASM pointer, wasm.cstrToJs(v) is
returned. Else v is returned as-is. returned. Else v is returned as-is.
*/ */
const flexibleString = function(v){ const flexibleString = function(v){
if(isSQLableTypedArray(v)) return typedArrayToString(v); if(isSQLableTypedArray(v)) return typedArrayToString(v);
else if(Array.isArray(v)) return v.join(""); else if(Array.isArray(v)) return v.join("");
else if(wasm.isPtr(v)) v = wasm.cstringToJs(v); else if(wasm.isPtr(v)) v = wasm.cstrToJs(v);
return v; return v;
}; };
@@ -565,7 +586,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
It returns its result and compiled statement as documented in It returns its result and compiled statement as documented in
the C API. Fetching the output pointers (5th and 6th the C API. Fetching the output pointers (5th and 6th
parameters) requires using `capi.wasm.getMemValue()` (or parameters) requires using `capi.wasm.peek()` (or
equivalent) and the `pzTail` will point to an address relative to equivalent) and the `pzTail` will point to an address relative to
the `sqlAsPointer` value. the `sqlAsPointer` value.
@@ -602,7 +623,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
If the callback is not a JS function then this binding performs If the callback is not a JS function then this binding performs
no translation of the callback, but the sql argument is still no translation of the callback, but the sql argument is still
converted to a WASM string for the call using the converted to a WASM string for the call using the
"flexible-string" argument converter. "string:flexible" argument converter.
*/ */
sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/, sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/,
@@ -857,171 +878,6 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
) ? !!capi.sqlite3_compileoption_used(optName) : false; ) ? !!capi.sqlite3_compileoption_used(optName) : false;
}/*compileOptionUsed()*/; }/*compileOptionUsed()*/;
/**
Signatures for the WASM-exported C-side functions. Each entry
is an array with 2+ elements:
[ "c-side name",
"result type" (wasm.xWrap() syntax),
[arg types in xWrap() syntax]
// ^^^ this needn't strictly be an array: it can be subsequent
// elements instead: [x,y,z] is equivalent to x,y,z
]
Note that support for the API-specific data types in the
result/argument type strings gets plugged in at a later phase in
the API initialization process.
*/
wasm.bindingSignatures = [
// Please keep these sorted by function name!
["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"],
["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*"
/* TODO: we should arguably write a custom wrapper which knows
how to handle Blob, TypedArrays, and JS strings. */
],
["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"],
["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"],
["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"],
["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"],
["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"],
["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int"
/* We should arguably create a hand-written binding of
bind_text() which does more flexible text conversion, along
the lines of sqlite3_prepare_v3(). The slightly problematic
part is the final argument (text destructor). */
],
["sqlite3_busy_timeout","int", "sqlite3*", "int"],
["sqlite3_close_v2", "int", "sqlite3*"],
["sqlite3_changes", "int", "sqlite3*"],
["sqlite3_clear_bindings","int", "sqlite3_stmt*"],
["sqlite3_column_blob","*", "sqlite3_stmt*", "int"],
["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"],
["sqlite3_column_count", "int", "sqlite3_stmt*"],
["sqlite3_column_double","f64", "sqlite3_stmt*", "int"],
["sqlite3_column_int","int", "sqlite3_stmt*", "int"],
["sqlite3_column_name","string", "sqlite3_stmt*", "int"],
["sqlite3_column_text","string", "sqlite3_stmt*", "int"],
["sqlite3_column_type","int", "sqlite3_stmt*", "int"],
["sqlite3_compileoption_get", "string", "int"],
["sqlite3_compileoption_used", "int", "string"],
/* sqlite3_create_function(), sqlite3_create_function_v2(), and
sqlite3_create_window_function() use hand-written bindings to
simplify handling of their function-type arguments. */
["sqlite3_data_count", "int", "sqlite3_stmt*"],
["sqlite3_db_filename", "string", "sqlite3*", "string"],
["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"],
["sqlite3_db_name", "string", "sqlite3*", "int"],
["sqlite3_deserialize", "int", "sqlite3*", "string", "*", "i64", "i64", "int"]
/* Careful! Short version: de/serialize() are problematic because they
might use a different allocator than the user for managing the
deserialized block. de/serialize() are ONLY safe to use with
sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */,
["sqlite3_errmsg", "string", "sqlite3*"],
["sqlite3_error_offset", "int", "sqlite3*"],
["sqlite3_errstr", "string", "int"],
/*["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**"
Handled seperately to perform translation of the callback
into a WASM-usable one. ],*/
["sqlite3_expanded_sql", "string", "sqlite3_stmt*"],
["sqlite3_extended_errcode", "int", "sqlite3*"],
["sqlite3_extended_result_codes", "int", "sqlite3*", "int"],
["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"],
["sqlite3_finalize", "int", "sqlite3_stmt*"],
["sqlite3_free", undefined,"*"],
["sqlite3_initialize", undefined],
/*["sqlite3_interrupt", undefined, "sqlite3*"
^^^ we cannot actually currently support this because JS is
single-threaded and we don't have a portable way to access a DB
from 2 SharedWorkers concurrently. ],*/
["sqlite3_libversion", "string"],
["sqlite3_libversion_number", "int"],
["sqlite3_malloc", "*","int"],
["sqlite3_open", "int", "string", "*"],
["sqlite3_open_v2", "int", "string", "*", "int", "string"],
/* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled
separately due to us requiring two different sets of semantics
for those, depending on how their SQL argument is provided. */
/* sqlite3_randomness() uses a hand-written wrapper to extend
the range of supported argument types. */
["sqlite3_realloc", "*","*","int"],
["sqlite3_reset", "int", "sqlite3_stmt*"],
["sqlite3_result_blob",undefined, "sqlite3_context*", "*", "int", "*"],
["sqlite3_result_double",undefined, "sqlite3_context*", "f64"],
["sqlite3_result_error",undefined, "sqlite3_context*", "string", "int"],
["sqlite3_result_error_code", undefined, "sqlite3_context*", "int"],
["sqlite3_result_error_nomem", undefined, "sqlite3_context*"],
["sqlite3_result_error_toobig", undefined, "sqlite3_context*"],
["sqlite3_result_int",undefined, "sqlite3_context*", "int"],
["sqlite3_result_null",undefined, "sqlite3_context*"],
["sqlite3_result_text",undefined, "sqlite3_context*", "string", "int", "*"],
["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"],
["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"],
["sqlite3_shutdown", undefined],
["sqlite3_sourceid", "string"],
["sqlite3_sql", "string", "sqlite3_stmt*"],
["sqlite3_step", "int", "sqlite3_stmt*"],
["sqlite3_strglob", "int", "string","string"],
["sqlite3_strlike", "int", "string","string","int"],
["sqlite3_trace_v2", "int", "sqlite3*", "int", "*", "*"],
["sqlite3_total_changes", "int", "sqlite3*"],
/* Note sqlite3_uri_...() has very specific requirements
for their first C-string arguments, so we cannot perform
any type conversion on those. */
["sqlite3_uri_boolean", "int", "sqlite3_filename", "string", "int"],
["sqlite3_uri_key", "string", "sqlite3_filename", "int"],
["sqlite3_uri_parameter", "string", "sqlite3_filename", "string"],
["sqlite3_user_data","void*", "sqlite3_context*"],
["sqlite3_value_blob", "*", "sqlite3_value*"],
["sqlite3_value_bytes","int", "sqlite3_value*"],
["sqlite3_value_double","f64", "sqlite3_value*"],
["sqlite3_value_int","int", "sqlite3_value*"],
["sqlite3_value_text", "string", "sqlite3_value*"],
["sqlite3_value_type", "int", "sqlite3_value*"],
["sqlite3_vfs_find", "*", "string"],
["sqlite3_vfs_register", "int", "sqlite3_vfs*", "int"],
["sqlite3_vfs_unregister", "int", "sqlite3_vfs*"]
]/*wasm.bindingSignatures*/;
if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){
/* ^^^ "the problem" is that this is an option feature and the
build-time function-export list does not currently take
optional features into account. */
wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]);
}
/**
Functions which require BigInt (int64) support are separated from
the others because we need to conditionally bind them or apply
dummy impls, depending on the capabilities of the environment.
*/
wasm.bindingSignatures.int64 = [
["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]],
["sqlite3_changes64","i64", ["sqlite3*"]],
["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]],
["sqlite3_malloc64", "*","i64"],
["sqlite3_msize", "i64", "*"],
["sqlite3_realloc64", "*","*", "i64"],
["sqlite3_result_int64",undefined, "*", "i64"],
["sqlite3_result_zeroblob64", "int", "*", "i64"],
["sqlite3_total_changes64", "i64", ["sqlite3*"]],
["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]],
["sqlite3_value_int64","i64", "sqlite3_value*"],
];
/**
Functions which are intended solely for API-internal use by the
WASM components, not client code. These get installed into
sqlite3.wasm. Some of them get exposed to clients via variants
named sqlite3_js_...().
*/
wasm.bindingSignatures.wasm = [
["sqlite3_wasm_db_reset", "int", "sqlite3*"],
["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"],
["sqlite3_wasm_vfs_create_file", "int",
"sqlite3_vfs*","string","*", "int"],
["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"]
];
/** /**
sqlite3.wasm.pstack (pseudo-stack) holds a special-case sqlite3.wasm.pstack (pseudo-stack) holds a special-case
stack-style allocator intended only for use with _small_ data of stack-style allocator intended only for use with _small_ data of
@@ -1051,7 +907,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
space managed by Emscripten's stack-management, so does not space managed by Emscripten's stack-management, so does not
collide with Emscripten-provided stack allocation APIs. The collide with Emscripten-provided stack allocation APIs. The
memory lives in the WASM heap and can be used with routines such memory lives in the WASM heap and can be used with routines such
as wasm.setMemValue() and any wasm.heap8u().slice(). as wasm.poke() and any wasm.heap8u().slice().
*/ */
wasm.pstack = Object.assign(Object.create(null),{ wasm.pstack = Object.assign(Object.create(null),{
/** /**
@@ -1110,7 +966,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
When a returned pointers will refer to a 64-bit value, e.g. a When a returned pointers will refer to a 64-bit value, e.g. a
double or int64, and that value must be written or fetched, double or int64, and that value must be written or fetched,
e.g. using wasm.setMemValue() or wasm.getMemValue(), it is e.g. using wasm.poke() or wasm.peek(), it is
important that the pointer in question be aligned to an 8-byte important that the pointer in question be aligned to an 8-byte
boundary or else it will not be fetched or written properly and boundary or else it will not be fetched or written properly and
will corrupt or read neighboring memory. will corrupt or read neighboring memory.
@@ -1297,7 +1153,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
let pVfs = capi.sqlite3_vfs_find(0); let pVfs = capi.sqlite3_vfs_find(0);
while(pVfs){ while(pVfs){
const oVfs = new capi.sqlite3_vfs(pVfs); const oVfs = new capi.sqlite3_vfs(pVfs);
rc.push(wasm.cstringToJs(oVfs.$zName)); rc.push(wasm.cstrToJs(oVfs.$zName));
pVfs = oVfs.$pNext; pVfs = oVfs.$pNext;
oVfs.dispose(); oVfs.dispose();
} }
@@ -1343,8 +1199,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
toss3("Database serialization failed with code", toss3("Database serialization failed with code",
sqlite3.capi.sqlite3_js_rc_str(rc)); sqlite3.capi.sqlite3_js_rc_str(rc));
} }
pOut = wasm.getPtrValue(ppOut); pOut = wasm.peekPtr(ppOut);
const nOut = wasm.getMemValue(pSize, 'i64'); const nOut = wasm.peek(pSize, 'i64');
rc = nOut rc = nOut
? wasm.heap8u().slice(pOut, pOut + Number(nOut)) ? wasm.heap8u().slice(pOut, pOut + Number(nOut))
: new Uint8Array(); : new Uint8Array();
@@ -1538,6 +1394,265 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
}/* main-window-only bits */ }/* main-window-only bits */
/**
Wraps all known variants of the C-side variadic
sqlite3_db_config().
Full docs: https://sqlite.org/c3ref/db_config.html
Returns capi.SQLITE_MISUSE if op is not a valid operation ID.
*/
capi.sqlite3_db_config = function f(pDb, op, ...args){
if(!this.s){
this.s = wasm.xWrap('sqlite3_wasm_db_config_s','int',
['sqlite3*', 'int', 'string:static']
/* MAINDBNAME requires a static string */);
this.pii = wasm.xWrap('sqlite3_wasm_db_config_pii', 'int',
['sqlite3*', 'int', '*','int', 'int']);
this.ip = wasm.xWrap('sqlite3_wasm_db_config_ip','int',
['sqlite3*', 'int', 'int','*']);
}
const c = capi;
switch(op){
case c.SQLITE_DBCONFIG_ENABLE_FKEY:
case c.SQLITE_DBCONFIG_ENABLE_TRIGGER:
case c.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
case c.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
case c.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
case c.SQLITE_DBCONFIG_ENABLE_QPSG:
case c.SQLITE_DBCONFIG_TRIGGER_EQP:
case c.SQLITE_DBCONFIG_RESET_DATABASE:
case c.SQLITE_DBCONFIG_DEFENSIVE:
case c.SQLITE_DBCONFIG_WRITABLE_SCHEMA:
case c.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE:
case c.SQLITE_DBCONFIG_DQS_DML:
case c.SQLITE_DBCONFIG_DQS_DDL:
case c.SQLITE_DBCONFIG_ENABLE_VIEW:
case c.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT:
case c.SQLITE_DBCONFIG_TRUSTED_SCHEMA:
return this.ip(pDb, op, args[0], args[1] || 0);
case c.SQLITE_DBCONFIG_LOOKASIDE:
return this.pii(pDb, op, args[0], args[1], args[2]);
case c.SQLITE_DBCONFIG_MAINDBNAME:
return this.s(pDb, op, args[0]);
default:
return c.SQLITE_MISUSE;
}
}.bind(Object.create(null));
/**
Given a (sqlite3_value*), this function attempts to convert it
to an equivalent JS value with as much fidelity as feasible and
return it.
By default it throws if it cannot determine any sensible
conversion. If passed a falsy second argument, it instead returns
`undefined` if no suitable conversion is found. Note that there
is no conversion from SQL to JS which results in the `undefined`
value, so `undefined` has an unambiguous meaning here. It will
always throw a WasmAllocError if allocating memory for a
conversion fails.
Caveats:
- It does not support sqlite3_value_to_pointer() conversions
because those require a type name string which this function
does not have and cannot sensibly be given at the level of the
API where this is used (e.g. automatically converting UDF
arguments). Clients using sqlite3_value_to_pointer(), and its
related APIs, will need to manage those themselves.
*/
capi.sqlite3_value_to_js = function(pVal,throwIfCannotConvert=true){
let arg;
const valType = capi.sqlite3_value_type(pVal);
switch(valType){
case capi.SQLITE_INTEGER:
if(wasm.bigIntEnabled){
arg = capi.sqlite3_value_int64(pVal);
if(util.bigIntFitsDouble(arg)) arg = Number(arg);
}
else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/;
break;
case capi.SQLITE_FLOAT:
arg = capi.sqlite3_value_double(pVal);
break;
case capi.SQLITE_TEXT:
arg = capi.sqlite3_value_text(pVal);
break;
case capi.SQLITE_BLOB:{
const n = capi.sqlite3_value_bytes(pVal);
const pBlob = capi.sqlite3_value_blob(pVal);
if(n && !pBlob) sqlite3.WasmAllocError.toss(
"Cannot allocate memory for blob argument of",n,"byte(s)"
);
arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null;
break;
}
case capi.SQLITE_NULL:
arg = null; break;
default:
if(throwIfCannotConvert){
toss3(capi.SQLITE_MISMATCH,
"Unhandled sqlite3_value_type():",valType);
}
arg = undefined;
}
return arg;
};
/**
Requires a C-style array of `sqlite3_value*` objects and the
number of entries in that array. Returns a JS array containing
the results of passing each C array entry to
sqlite3_value_to_js(). The 3rd argument to this function is
passed on as the 2nd argument to that one.
*/
capi.sqlite3_values_to_js = function(argc,pArgv,throwIfCannotConvert=true){
let i;
const tgt = [];
for(i = 0; i < argc; ++i){
/**
Curiously: despite ostensibly requiring 8-byte
alignment, the pArgv array is parcelled into chunks of
4 bytes (1 pointer each). The values those point to
have 8-byte alignment but the individual argv entries
do not.
*/
tgt.push(capi.sqlite3_value_to_js(
wasm.peekPtr(pArgv + (wasm.ptrSizeof * i))
));
}
return tgt;
};
/**
Calls either sqlite3_result_error_nomem(), if e is-a
WasmAllocError, or sqlite3_result_error(). In the latter case,
the second arugment is coerced to a string to create the error
message.
The first argument is a (sqlite3_context*). Returns void.
Does not throw.
*/
capi.sqlite3_result_error_js = function(pCtx,e){
if(e instanceof WasmAllocError){
capi.sqlite3_result_error_nomem(pCtx);
}else{
/* Maintenance reminder: ''+e, rather than e.message,
will prefix e.message with e.name, so it includes
the exception's type name in the result. */;
capi.sqlite3_result_error(pCtx, ''+e, -1);
}
};
/**
This function passes its 2nd argument to one of the
sqlite3_result_xyz() routines, depending on the type of that
argument:
- If (val instanceof Error), this function passes it to
sqlite3_result_error_js().
- `null`: `sqlite3_result_null()`
- `boolean`: `sqlite3_result_int()` with a value of 0 or 1.
- `number`: `sqlite3_result_int()`, `sqlite3_result_int64()`, or
`sqlite3_result_double()`, depending on the range of the number
and whether or not int64 support is enabled.
- `bigint`: similar to `number` but will trigger an error if the
value is too big to store in an int64.
- `string`: `sqlite3_result_text()`
- Uint8Array or Int8Array: `sqlite3_result_blob()`
- `undefined`: is a no-op provided to simplify certain use cases.
Anything else triggers `sqlite3_result_error()` with a
description of the problem.
The first argument to this function is a `(sqlite3_context*)`.
Returns void. Does not throw.
*/
capi.sqlite3_result_js = function(pCtx,val){
if(val instanceof Error){
capi.sqlite3_result_error_js(pCtx, val);
return;
}
try{
switch(typeof val) {
case 'undefined':
/* This is a no-op. This routine originated in the create_function()
family of APIs and in that context, passing in undefined indicated
that the caller was responsible for calling sqlite3_result_xxx()
(if needed). */
break;
case 'boolean':
capi.sqlite3_result_int(pCtx, val ? 1 : 0);
break;
case 'bigint':
if(util.bigIntFits32(val)){
capi.sqlite3_result_int(pCtx, Number(val));
}else if(util.bigIntFitsDouble(val)){
capi.sqlite3_result_double(pCtx, Number(val));
}else if(wasm.bigIntEnabled){
if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val);
else toss3("BigInt value",val.toString(),"is too BigInt for int64.");
}else{
toss3("BigInt value",val.toString(),"is too BigInt.");
}
break;
case 'number': {
let f;
if(util.isInt32(val)){
f = capi.sqlite3_result_int;
}else if(wasm.bigIntEnabled
&& Number.isInteger(val)
&& util.bigIntFits64(BigInt(val))){
f = capi.sqlite3_result_int64;
}else{
f = capi.sqlite3_result_double;
}
f(pCtx, val);
break;
}
case 'string':
capi.sqlite3_result_text(pCtx, val, -1, capi.SQLITE_TRANSIENT);
break;
case 'object':
if(null===val/*yes, typeof null === 'object'*/) {
capi.sqlite3_result_null(pCtx);
break;
}else if(util.isBindableTypedArray(val)){
const pBlob = wasm.allocFromTypedArray(val);
capi.sqlite3_result_blob(
pCtx, pBlob, val.byteLength,
wasm.exports[sqlite3.config.deallocExportName]
);
break;
}
// else fall through
default:
toss3("Don't not how to handle this UDF result value:",(typeof val), val);
}
}catch(e){
capi.sqlite3_result_error_js(pCtx, e);
}
};
/**
Returns the result sqlite3_column_value(pStmt,iCol) passed to
sqlite3_value_to_js(). The 3rd argument of this function is
ignored by this function except to pass it on as the second
argument of sqlite3_value_to_js(). If the sqlite3_column_value()
returns NULL (e.g. because the column index is out of range),
this function returns `undefined`, regardless of the 3rd
argument. 3rd argument is falsy and conversion fails, `undefined`
will be returned.
Note that sqlite3_column_value() returns an "unprotected" value
object, but in a single-threaded environment (like this one)
there is no distinction between protected and unprotected values.
*/
capi.sqlite3_column_js = function(pStmt, iCol, throwIfCannotConvert=true){
const v = capi.sqlite3_column_value(pStmt, iCol);
return (0===v) ? undefined : capi.sqlite3_value_to_js(v, throwIfCannotConvert);
};
/* The remainder of the API will be set up in later steps. */ /* The remainder of the API will be set up in later steps. */
const sqlite3 = { const sqlite3 = {
@@ -1600,7 +1715,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
some initializers. Retain them when running in test mode some initializers. Retain them when running in test mode
so that we can add tests for them. */ so that we can add tests for them. */
delete sqlite3.util; delete sqlite3.util;
delete sqlite3.VfsHelper; /* It's conceivable that we might want to expose
StructBinder to client-side code, but it's only useful if
clients build their own sqlite3.wasm which contains their
one C struct types. */
delete sqlite3.StructBinder; delete sqlite3.StructBinder;
} }
return sqlite3; return sqlite3;

View File

@@ -13,7 +13,7 @@
A Worker which manages asynchronous OPFS handles on behalf of a A Worker which manages asynchronous OPFS handles on behalf of a
synchronous API which controls it via a combination of Worker synchronous API which controls it via a combination of Worker
messages, SharedArrayBuffer, and Atomics. It is the asynchronous messages, SharedArrayBuffer, and Atomics. It is the asynchronous
counterpart of the API defined in sqlite3-api-opfs.js. counterpart of the API defined in sqlite3-vfs-opfs.js.
Highly indebted to: Highly indebted to:
@@ -343,16 +343,6 @@ const installAsyncProxy = function(self){
const affirmNotRO = function(opName,fh){ const affirmNotRO = function(opName,fh){
if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs); if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs);
}; };
const affirmLocked = function(opName,fh){
//if(!fh.syncHandle) toss(opName+"(): File does not have a lock: "+fh.filenameAbs);
/**
Currently a no-op, as speedtest1 triggers xRead() without a
lock (that seems like a bug but it's currently uninvestigated).
This means, however, that some OPFS VFS routines may trigger
acquisition of a lock but never let it go until xUnlock() is
called (which it likely won't be if xLock() was not called).
*/
};
/** /**
We track 2 different timers: the "metrics" timer records how much We track 2 different timers: the "metrics" timer records how much
@@ -393,7 +383,6 @@ const installAsyncProxy = function(self){
*/ */
let flagAsyncShutdown = false; let flagAsyncShutdown = false;
/** /**
Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods
methods, as well as helpers like mkdir(). Maintenance reminder: methods, as well as helpers like mkdir(). Maintenance reminder:
@@ -427,11 +416,11 @@ const installAsyncProxy = function(self){
}, },
xAccess: async (filename)=>{ xAccess: async (filename)=>{
mTimeStart('xAccess'); mTimeStart('xAccess');
/* OPFS cannot support the full range of xAccess() queries sqlite3 /* OPFS cannot support the full range of xAccess() queries
calls for. We can essentially just tell if the file is sqlite3 calls for. We can essentially just tell if the file
accessible, but if it is it's automatically writable (unless is accessible, but if it is then it's automatically writable
it's locked, which we cannot(?) know without trying to open (unless it's locked, which we cannot(?) know without trying
it). OPFS does not have the notion of read-only. to open it). OPFS does not have the notion of read-only.
The return semantics of this function differ from sqlite3's The return semantics of this function differ from sqlite3's
xAccess semantics because we are limited in what we can xAccess semantics because we are limited in what we can
@@ -519,7 +508,6 @@ const installAsyncProxy = function(self){
let rc = 0; let rc = 0;
wTimeStart('xFileSize'); wTimeStart('xFileSize');
try{ try{
affirmLocked('xFileSize',fh);
const sz = await (await getSyncHandle(fh,'xFileSize')).getSize(); const sz = await (await getSyncHandle(fh,'xFileSize')).getSize();
state.s11n.serialize(Number(sz)); state.s11n.serialize(Number(sz));
}catch(e){ }catch(e){
@@ -615,7 +603,6 @@ const installAsyncProxy = function(self){
let rc = 0, nRead; let rc = 0, nRead;
const fh = __openFiles[fid]; const fh = __openFiles[fid];
try{ try{
affirmLocked('xRead',fh);
wTimeStart('xRead'); wTimeStart('xRead');
nRead = (await getSyncHandle(fh,'xRead')).read( nRead = (await getSyncHandle(fh,'xRead')).read(
fh.sabView.subarray(0, n), fh.sabView.subarray(0, n),
@@ -659,7 +646,6 @@ const installAsyncProxy = function(self){
const fh = __openFiles[fid]; const fh = __openFiles[fid];
wTimeStart('xTruncate'); wTimeStart('xTruncate');
try{ try{
affirmLocked('xTruncate',fh);
affirmNotRO('xTruncate', fh); affirmNotRO('xTruncate', fh);
await (await getSyncHandle(fh,'xTruncate')).truncate(size); await (await getSyncHandle(fh,'xTruncate')).truncate(size);
}catch(e){ }catch(e){
@@ -696,7 +682,6 @@ const installAsyncProxy = function(self){
const fh = __openFiles[fid]; const fh = __openFiles[fid];
wTimeStart('xWrite'); wTimeStart('xWrite');
try{ try{
affirmLocked('xWrite',fh);
affirmNotRO('xWrite', fh); affirmNotRO('xWrite', fh);
rc = ( rc = (
n === (await getSyncHandle(fh,'xWrite')) n === (await getSyncHandle(fh,'xWrite'))

View File

@@ -0,0 +1,714 @@
/*
** 2022-11-30
**
** The author disclaims copyright to this source code. In place of a
** legal notice, here is a blessing:
**
** * May you do good and not evil.
** * May you find forgiveness for yourself and forgive others.
** * May you share freely, never taking more than you give.
*/
/**
This file installs sqlite3.vfs, and object which exists to assist
in the creation of JavaScript implementations of sqlite3_vfs, along
with its virtual table counterpart, sqlite3.vtab.
*/
'use strict';
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
const vfs = Object.create(null), vtab = Object.create(null);
sqlite3.vfs = vfs;
sqlite3.vtab = vtab;
const sii = capi.sqlite3_index_info;
/**
If n is >=0 and less than this.$nConstraint, this function
returns either a WASM pointer to the 0-based nth entry of
this.$aConstraint (if passed a truthy 2nd argument) or an
sqlite3_index_info.sqlite3_index_constraint object wrapping that
address (if passed a falsy value or no 2nd argument). Returns a
falsy value if n is out of range.
*/
sii.prototype.nthConstraint = function(n, asPtr=false){
if(n<0 || n>=this.$nConstraint) return false;
const ptr = this.$aConstraint + (
sii.sqlite3_index_constraint.structInfo.sizeof * n
);
return asPtr ? ptr : new sii.sqlite3_index_constraint(ptr);
};
/**
Works identically to nthConstraint() but returns state from
this.$aConstraintUsage, so returns an
sqlite3_index_info.sqlite3_index_constraint_usage instance
if passed no 2nd argument or a falsy 2nd argument.
*/
sii.prototype.nthConstraintUsage = function(n, asPtr=false){
if(n<0 || n>=this.$nConstraint) return false;
const ptr = this.$aConstraintUsage + (
sii.sqlite3_index_constraint_usage.structInfo.sizeof * n
);
return asPtr ? ptr : new sii.sqlite3_index_constraint_usage(ptr);
};
/**
If n is >=0 and less than this.$nOrderBy, this function
returns either a WASM pointer to the 0-based nth entry of
this.$aOrderBy (if passed a truthy 2nd argument) or an
sqlite3_index_info.sqlite3_index_orderby object wrapping that
address (if passed a falsy value or no 2nd argument). Returns a
falsy value if n is out of range.
*/
sii.prototype.nthOrderBy = function(n, asPtr=false){
if(n<0 || n>=this.$nOrderBy) return false;
const ptr = this.$aOrderBy + (
sii.sqlite3_index_orderby.structInfo.sizeof * n
);
return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr);
};
/**
Installs a StructBinder-bound function pointer member of the
given name and function in the given StructType target object.
It creates a WASM proxy for the given function and arranges for
that proxy to be cleaned up when tgt.dispose() is called. Throws
on the slightest hint of error, e.g. tgt is-not-a StructType,
name does not map to a struct-bound member, etc.
As a special case, if the given function is a pointer, then
`wasm.functionEntry()` is used to validate that it is a known
function. If so, it is used as-is with no extra level of proxying
or cleanup, else an exception is thrown. It is legal to pass a
value of 0, indicating a NULL pointer, with the caveat that 0
_is_ a legal function pointer in WASM but it will not be accepted
as such _here_. (Justification: the function at address zero must
be one which initially came from the WASM module, not a method we
want to bind to a virtual table or VFS.)
This function returns a proxy for itself which is bound to tgt
and takes 2 args (name,func). That function returns the same
thing as this one, permitting calls to be chained.
If called with only 1 arg, it has no side effects but returns a
func with the same signature as described above.
ACHTUNG: because we cannot generically know how to transform JS
exceptions into result codes, the installed functions do no
automatic catching of exceptions. It is critical, to avoid
undefined behavior in the C layer, that methods mapped via
this function do not throw. The exception, as it were, to that
rule is...
If applyArgcCheck is true then each JS function (as opposed to
function pointers) gets wrapped in a proxy which asserts that it
is passed the expected number of arguments, throwing if the
argument count does not match expectations. That is only intended
for dev-time usage for sanity checking, and will leave the C
environment in an undefined state.
*/
const installMethod = function callee(
tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck
){
if(!(tgt instanceof sqlite3.StructBinder.StructType)){
toss("Usage error: target object is-not-a StructType.");
}else if(!(func instanceof Function) && !wasm.isPtr(func)){
toss("Usage errror: expecting a Function or WASM pointer to one.");
}
if(1===arguments.length){
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}
if(!callee.argcProxy){
callee.argcProxy = function(tgt, funcName, func,sig){
return function(...args){
if(func.length!==arguments.length){
toss("Argument mismatch for",
tgt.structInfo.name+"::"+funcName
+": Native signature is:",sig);
}
return func.apply(this, args);
}
};
/* An ondispose() callback for use with
sqlite3.StructBinder-created types. */
callee.removeFuncList = function(){
if(this.ondispose.__removeFuncList){
this.ondispose.__removeFuncList.forEach(
(v,ndx)=>{
if('number'===typeof v){
try{wasm.uninstallFunction(v)}
catch(e){/*ignore*/}
}
/* else it's a descriptive label for the next number in
the list. */
}
);
delete this.ondispose.__removeFuncList;
}
};
}/*static init*/
const sigN = tgt.memberSignature(name);
if(sigN.length<2){
toss("Member",name,"does not have a function pointer signature:",sigN);
}
const memKey = tgt.memberKey(name);
const fProxy = (applyArgcCheck && !wasm.isPtr(func))
/** This middle-man proxy is only for use during development, to
confirm that we always pass the proper number of
arguments. We know that the C-level code will always use the
correct argument count. */
? callee.argcProxy(tgt, memKey, func, sigN)
: func;
if(wasm.isPtr(fProxy)){
if(fProxy && !wasm.functionEntry(fProxy)){
toss("Pointer",fProxy,"is not a WASM function table entry.");
}
tgt[memKey] = fProxy;
}else{
const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
tgt[memKey] = pFunc;
if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){
tgt.addOnDispose('ondispose.__removeFuncList handler',
callee.removeFuncList);
tgt.ondispose.__removeFuncList = [];
}
tgt.ondispose.__removeFuncList.push(memKey, pFunc);
}
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}/*installMethod*/;
installMethod.installMethodArgcCheck = false;
/**
Installs methods into the given StructType-type instance. Each
entry in the given methods object must map to a known member of
the given StructType, else an exception will be triggered. See
installMethod() for more details, including the semantics of the
3rd argument.
As an exception to the above, if any two or more methods in the
2nd argument are the exact same function, installMethod() is
_not_ called for the 2nd and subsequent instances, and instead
those instances get assigned the same method pointer which is
created for the first instance. This optimization is primarily to
accommodate special handling of sqlite3_module::xConnect and
xCreate methods.
On success, returns its first argument. Throws on error.
*/
const installMethods = function(
structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck
){
const seen = new Map /* map of <Function, memberName> */;
for(const k of Object.keys(methods)){
const m = methods[k];
const prior = seen.get(m);
if(prior){
const mkey = structInstance.memberKey(k);
structInstance[mkey] = structInstance[structInstance.memberKey(prior)];
}else{
installMethod(structInstance, k, m, applyArgcCheck);
seen.set(m, k);
}
}
return structInstance;
};
/**
Equivalent to calling installMethod(this,...arguments) with a
first argument of this object. If called with 1 or 2 arguments
and the first is an object, it's instead equivalent to calling
installMethods(this,...arguments).
*/
sqlite3.StructBinder.StructType.prototype.installMethod = function callee(
name, func, applyArgcCheck = installMethod.installMethodArgcCheck
){
return (arguments.length < 3 && name && 'object'===typeof name)
? installMethods(this, ...arguments)
: installMethod(this, ...arguments);
};
/**
Equivalent to calling installMethods() with a first argument
of this object.
*/
sqlite3.StructBinder.StructType.prototype.installMethods = function(
methods, applyArgcCheck = installMethod.installMethodArgcCheck
){
return installMethods(this, methods, applyArgcCheck);
};
/**
Uses sqlite3_vfs_register() to register this
sqlite3.capi.sqlite3_vfs. This object must have already been
filled out properly. If the first argument is truthy, the VFS is
registered as the default VFS, else it is not.
On success, returns this object. Throws on error.
*/
capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){
if(!(this instanceof sqlite3.capi.sqlite3_vfs)){
toss("Expecting a sqlite3_vfs-type argument.");
}
const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0);
if(rc){
toss("sqlite3_vfs_register(",this,") failed with rc",rc);
}
if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){
toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
this);
}
return this;
};
/**
A wrapper for installMethods() or registerVfs() to reduce
installation of a VFS and/or its I/O methods to a single
call.
Accepts an object which contains the properties "io" and/or
"vfs", each of which is itself an object with following properties:
- `struct`: an sqlite3.StructType-type struct. This must be a
populated (except for the methods) object of type
sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the
"vfs" entry).
- `methods`: an object mapping sqlite3_io_methods method names
(e.g. 'xClose') to JS implementations of those methods. The JS
implementations must be call-compatible with their native
counterparts.
For each of those object, this function passes its (`struct`,
`methods`, (optional) `applyArgcCheck`) properties to
installMethods().
If the `vfs` entry is set then:
- Its `struct` property's registerVfs() is called. The
`vfs` entry may optionally have an `asDefault` property, which
gets passed as the argument to registerVfs().
- If `struct.$zName` is falsy and the entry has a string-type
`name` property, `struct.$zName` is set to the C-string form of
that `name` value before registerVfs() is called.
On success returns this object. Throws on error.
*/
vfs.installVfs = function(opt){
let count = 0;
const propList = ['io','vfs'];
for(const key of propList){
const o = opt[key];
if(o){
++count;
installMethods(o.struct, o.methods, !!o.applyArgcCheck);
if('vfs'===key){
if(!o.struct.$zName && 'string'===typeof o.name){
o.struct.addOnDispose(
o.struct.$zName = wasm.allocCString(o.name)
);
}
o.struct.registerVfs(!!o.asDefault);
}
}
}
if(!count) toss("Misuse: installVfs() options object requires at least",
"one of:", propList);
return this;
};
/**
Internal factory function for xVtab and xCursor impls.
*/
const __xWrapFactory = function(methodName,StructType){
return function(ptr,removeMapping=false){
if(0===arguments.length) ptr = new StructType;
if(ptr instanceof StructType){
//T.assert(!this.has(ptr.pointer));
this.set(ptr.pointer, ptr);
return ptr;
}else if(!wasm.isPtr(ptr)){
sqlite3.SQLite3Error.toss("Invalid argument to",methodName+"()");
}
let rc = this.get(ptr);
if(removeMapping) this.delete(ptr);
return rc;
}.bind(new Map);
};
/**
A factory function which implements a simple lifetime manager for
mappings between C struct pointers and their JS-level wrappers.
The first argument must be the logical name of the manager
(e.g. 'xVtab' or 'xCursor'), which is only used for error
reporting. The second must be the capi.XYZ struct-type value,
e.g. capi.sqlite3_vtab or capi.sqlite3_vtab_cursor.
Returns an object with 4 methods: create(), get(), unget(), and
dispose(), plus a StructType member with the value of the 2nd
argument. The methods are documented in the body of this
function.
*/
const StructPtrMapper = function(name, StructType){
const __xWrap = __xWrapFactory(name,StructType);
/**
This object houses a small API for managing mappings of (`T*`)
to StructType<T> objects, specifically within the lifetime
requirements of sqlite3_module methods.
*/
return Object.assign(Object.create(null),{
/** The StructType object for this object's API. */
StructType,
/**
Creates a new StructType object, writes its `pointer`
value to the given output pointer, and returns that
object. Its intended usage depends on StructType:
sqlite3_vtab: to be called from sqlite3_module::xConnect()
or xCreate() implementations.
sqlite3_vtab_cursor: to be called from xOpen().
This will throw if allocation of the StructType instance
fails or if ppOut is not a pointer-type value.
*/
create: (ppOut)=>{
const rc = __xWrap();
wasm.pokePtr(ppOut, rc.pointer);
return rc;
},
/**
Returns the StructType object previously mapped to the
given pointer using create(). Its intended usage depends
on StructType:
sqlite3_vtab: to be called from sqlite3_module methods which
take a (sqlite3_vtab*) pointer _except_ for
xDestroy()/xDisconnect(), in which case unget() or dispose().
sqlite3_vtab_cursor: to be called from any sqlite3_module methods
which take a `sqlite3_vtab_cursor*` argument except xClose(),
in which case use unget() or dispose().
Rule to remember: _never_ call dispose() on an instance
returned by this function.
*/
get: (pCObj)=>__xWrap(pCObj),
/**
Identical to get() but also disconnects the mapping between the
given pointer and the returned StructType object, such that
future calls to this function or get() with the same pointer
will return the undefined value. Its intended usage depends
on StructType:
sqlite3_vtab: to be called from sqlite3_module::xDisconnect() or
xDestroy() implementations or in error handling of a failed
xCreate() or xConnect().
sqlite3_vtab_cursor: to be called from xClose() or during
cleanup in a failed xOpen().
Calling this method obligates the caller to call dispose() on
the returned object when they're done with it.
*/
unget: (pCObj)=>__xWrap(pCObj,true),
/**
Works like unget() plus it calls dispose() on the
StructType object.
*/
dispose: (pCObj)=>{
const o = __xWrap(pCObj,true);
if(o) o.dispose();
}
});
};
/**
A lifetime-management object for mapping `sqlite3_vtab*`
instances in sqlite3_module methods to capi.sqlite3_vtab
objects.
The API docs are in the API-internal StructPtrMapper().
*/
vtab.xVtab = StructPtrMapper('xVtab', capi.sqlite3_vtab);
/**
A lifetime-management object for mapping `sqlite3_vtab_cursor*`
instances in sqlite3_module methods to capi.sqlite3_vtab_cursor
objects.
The API docs are in the API-internal StructPtrMapper().
*/
vtab.xCursor = StructPtrMapper('xCursor', capi.sqlite3_vtab_cursor);
/**
Convenience form of creating an sqlite3_index_info wrapper,
intended for use in xBestIndex implementations. Note that the
caller is expected to call dispose() on the returned object
before returning. Though not _strictly_ required, as that object
does not own the pIdxInfo memory, it is nonetheless good form.
*/
vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo);
/**
Given an error object, this function returns
sqlite3.capi.SQLITE_NOMEM if (e instanceof
sqlite3.WasmAllocError), else it returns its
second argument. Its intended usage is in the methods
of a sqlite3_vfs or sqlite3_module:
```
try{
let rc = ...
return rc;
}catch(e){
return sqlite3.vtab.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ);
// where SQLITE_XYZ is some call-appropriate result code.
}
```
*/
/**vfs.exceptionToRc = vtab.exceptionToRc =
(e, defaultRc=capi.SQLITE_ERROR)=>(
(e instanceof sqlite3.WasmAllocError)
? capi.SQLITE_NOMEM
: defaultRc
);*/
/**
Given an sqlite3_module method name and error object, this
function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof
sqlite3.WasmAllocError), else it returns its second argument. Its
intended usage is in the methods of a sqlite3_vfs or
sqlite3_module:
```
try{
let rc = ...
return rc;
}catch(e){
return sqlite3.vtab.xError(
'xColumn', e, sqlite3.capi.SQLITE_XYZ);
// where SQLITE_XYZ is some call-appropriate result code.
}
```
If no 3rd argument is provided, its default depends on
the error type:
- An sqlite3.WasmAllocError always resolves to capi.SQLITE_NOMEM.
- If err is an SQLite3Error then its `resultCode` property
is used.
- If all else fails, capi.SQLITE_ERROR is used.
If xError.errorReporter is a function, it is called in
order to report the error, else the error is not reported.
If that function throws, that exception is ignored.
*/
vtab.xError = function f(methodName, err, defaultRc){
if(f.errorReporter instanceof Function){
try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);}
catch(e){/*ignored*/}
}
let rc;
if(err instanceof sqlite3.WasmAllocError) rc = capi.SQLITE_NOMEM;
else if(arguments.length>2) rc = defaultRc;
else if(err instanceof sqlite3.SQLite3Error) rc = err.resultCode;
return rc || capi.SQLITE_ERROR;
};
vtab.xError.errorReporter = 1 ? console.error.bind(console) : false;
/**
"The problem" with this is that it introduces an outer function with
a different arity than the passed-in method callback. That means we
cannot do argc validation on these. Additionally, some methods (namely
xConnect) may have call-specific error handling. It would be a shame to
hard-coded that per-method support in this function.
*/
/** vtab.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){
return function(...args){
try { method(...args); }
}catch(e){ return vtab.xError(methodName, e, defaultRc) }
};
*/
/**
A helper for sqlite3_vtab::xRowid() and xUpdate()
implementations. It must be passed the final argument to one of
those methods (an output pointer to an int64 row ID) and the
value to store at the output pointer's address. Returns the same
as wasm.poke() and will throw if the 1st or 2nd arguments
are invalid for that function.
Example xRowid impl:
```
const xRowid = (pCursor, ppRowid64)=>{
const c = vtab.xCursor(pCursor);
vtab.xRowid(ppRowid64, c.myRowId);
return 0;
};
```
*/
vtab.xRowid = (ppRowid64, value)=>wasm.poke(ppRowid64, value, 'i64');
/**
A helper to initialize and set up an sqlite3_module object for
later installation into individual databases using
sqlite3_create_module(). Requires an object with the following
properties:
- `methods`: an object containing a mapping of properties with
the C-side names of the sqlite3_module methods, e.g. xCreate,
xBestIndex, etc., to JS implementations for those functions.
Certain special-case handling is performed, as described below.
- `catchExceptions` (default=false): if truthy, the given methods
are not mapped as-is, but are instead wrapped inside wrappers
which translate exceptions into result codes of SQLITE_ERROR or
SQLITE_NOMEM, depending on whether the exception is an
sqlite3.WasmAllocError. In the case of the xConnect and xCreate
methods, the exception handler also sets the output error
string to the exception's error string.
- OPTIONAL `struct`: a sqlite3.capi.sqlite3_module() instance. If
not set, one will be created automatically. If the current
"this" is-a sqlite3_module then it is unconditionally used in
place of `struct`.
- OPTIONAL `iVersion`: if set, it must be an integer value and it
gets assigned to the `$iVersion` member of the struct object.
If it's _not_ set, and the passed-in `struct` object's `$iVersion`
is 0 (the default) then this function attempts to define a value
for that property based on the list of methods it has.
If `catchExceptions` is false, it is up to the client to ensure
that no exceptions escape the methods, as doing so would move
them through the C API, leading to undefined
behavior. (vtab.xError() is intended to assist in reporting
such exceptions.)
Certain methods may refer to the same implementation. To simplify
the definition of such methods:
- If `methods.xConnect` is `true` then the value of
`methods.xCreate` is used in its place, and vice versa. sqlite
treats xConnect/xCreate functions specially if they are exactly
the same function (same pointer value).
- If `methods.xDisconnect` is true then the value of
`methods.xDestroy` is used in its place, and vice versa.
This is to facilitate creation of those methods inline in the
passed-in object without requiring the client to explicitly get a
reference to one of them in order to assign it to the other
one.
The `catchExceptions`-installed handlers will account for
identical references to the above functions and will install the
same wrapper function for both.
The given methods are expected to return integer values, as
expected by the C API. If `catchExceptions` is truthy, the return
value of the wrapped function will be used as-is and will be
translated to 0 if the function returns a falsy value (e.g. if it
does not have an explicit return). If `catchExceptions` is _not_
active, the method implementations must explicitly return integer
values.
Throws on error. On success, returns the sqlite3_module object
(`this` or `opt.struct` or a new sqlite3_module instance,
depending on how it's called).
*/
vtab.setupModule = function(opt){
let createdMod = false;
const mod = (this instanceof capi.sqlite3_module)
? this : (opt.struct || (createdMod = new capi.sqlite3_module()));
try{
const methods = opt.methods || toss("Missing 'methods' object.");
for(const e of Object.entries({
// -----^ ==> [k,v] triggers a broken code transformation in
// some versions of the emsdk toolchain.
xConnect: 'xCreate', xDisconnect: 'xDestroy'
})){
// Remap X=true to X=Y for certain X/Y combinations
const k = e[0], v = e[1];
if(true === methods[k]) methods[k] = methods[v];
else if(true === methods[v]) methods[v] = methods[k];
}
if(opt.catchExceptions){
const fwrap = function(methodName, func){
if(['xConnect','xCreate'].indexOf(methodName) >= 0){
return function(pDb, pAux, argc, argv, ppVtab, pzErr){
try{return func(...arguments) || 0}
catch(e){
if(!(e instanceof sqlite3.WasmAllocError)){
wasm.dealloc(wasm.peekPtr(pzErr));
wasm.pokePtr(pzErr, wasm.allocCString(e.message));
}
return vtab.xError(methodName, e);
}
};
}else{
return function(...args){
try{return func(...args) || 0}
catch(e){
return vtab.xError(methodName, e);
}
};
}
};
const mnames = [
'xCreate', 'xConnect', 'xBestIndex', 'xDisconnect',
'xDestroy', 'xOpen', 'xClose', 'xFilter', 'xNext',
'xEof', 'xColumn', 'xRowid', 'xUpdate',
'xBegin', 'xSync', 'xCommit', 'xRollback',
'xFindFunction', 'xRename', 'xSavepoint', 'xRelease',
'xRollbackTo', 'xShadowName'
];
const remethods = Object.create(null);
for(const k of mnames){
const m = methods[k];
if(!(m instanceof Function)) continue;
else if('xConnect'===k && methods.xCreate===m){
remethods[k] = methods.xCreate;
}else if('xCreate'===k && methods.xConnect===m){
remethods[k] = methods.xConnect;
}else{
remethods[k] = fwrap(k, m);
}
}
installMethods(mod, remethods, false);
}else{
// No automatic exception handling. Trust the client
// to not throw.
installMethods(
mod, methods, !!opt.applyArgcCheck/*undocumented option*/
);
}
if(0===mod.$iVersion){
let v;
if('number'===typeof opt.iVersion) v = opt.iVersion;
else if(mod.$xShadowName) v = 3;
else if(mod.$xSavePoint || mod.$xRelease || mod.$xRollbackTo) v = 2;
else v = 1;
mod.$iVersion = v;
}
}catch(e){
if(createdMod) createdMod.dispose();
throw e;
}
return mod;
}/*setupModule()*/;
/**
Equivalent to calling vtab.setupModule() with this sqlite3_module
object as the call's `this`.
*/
capi.sqlite3_module.prototype.setupModule = function(opt){
return vtab.setupModule.call(this, opt);
};
}/*sqlite3ApiBootstrap.initializers.push()*/);

View File

@@ -1,221 +0,0 @@
/*
** 2022-11-30
**
** The author disclaims copyright to this source code. In place of a
** legal notice, here is a blessing:
**
** * May you do good and not evil.
** * May you find forgiveness for yourself and forgive others.
** * May you share freely, never taking more than you give.
*/
/**
This file installs sqlite.VfsHelper, an object which exists
to assist in the creation of JavaScript implementations of
sqlite3_vfs. It is NOT part of the public API, and is an
internal implemenation detail for use in this project's
own development of VFSes. It may be exposed to clients
at some point, provided there is value in doing so.
*/
'use strict';
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss;
const vh = Object.create(null);
/**
Does nothing more than holds a permanent reference to each
argument. This is useful in some cases to ensure that, e.g., a
custom sqlite3_io_methods instance does not get
garbage-collected.
Returns this object.
*/
vh.holdReference = function(...args){
for(const v of args) this.refs.add(v);
return vh;
}.bind({refs: new Set});
/**
Installs a StructBinder-bound function pointer member of the
given name and function in the given StructType target object.
It creates a WASM proxy for the given function and arranges for
that proxy to be cleaned up when tgt.dispose() is called. Throws
on the slightest hint of error, e.g. tgt is-not-a StructType,
name does not map to a struct-bound member, etc.
If applyArgcCheck is true then each method gets wrapped in a
proxy which asserts that it is passed the expected number of
arguments, throwing if the argument count does not match
expectations. That is only recommended for dev-time usage for
sanity checking. Once a VFS implementation is known to be
working, it is a given that the C API will never call it with the
wrong argument count.
Returns a proxy for this function which is bound to tgt and takes
2 args (name,func). That function returns the same thing,
permitting calls to be chained.
If called with only 1 arg, it has no side effects but returns a
func with the same signature as described above.
If tgt.ondispose is set before this is called then it _must_
be an array, to which this function will append entries.
*/
vh.installMethod = function callee(tgt, name, func,
applyArgcCheck=callee.installMethodArgcCheck){
if(!(tgt instanceof sqlite3.StructBinder.StructType)){
toss("Usage error: target object is-not-a StructType.");
}
if(1===arguments.length){
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}
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);
}
};
/* An ondispose() callback for use with
sqlite3.StructBinder-created types. */
callee.removeFuncList = function(){
if(this.ondispose.__removeFuncList){
this.ondispose.__removeFuncList.forEach(
(v,ndx)=>{
if('number'===typeof v){
try{wasm.uninstallFunction(v)}
catch(e){/*ignore*/}
}
/* else it's a descriptive label for the next number in
the list. */
}
);
delete this.ondispose.__removeFuncList;
}
};
}/*static init*/
const sigN = tgt.memberSignature(name);
if(sigN.length<2){
toss("Member",name," is not a function pointer. Signature =",sigN);
}
const memKey = tgt.memberKey(name);
const fProxy = applyArgcCheck
/** This middle-man proxy is only for use during development, to
confirm that we always pass the proper number of
arguments. We know that the C-level code will always use the
correct argument count. */
? callee.argcProxy(func, sigN)
: func;
const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
tgt[memKey] = pFunc;
if(!tgt.ondispose) tgt.ondispose = [];
if(!tgt.ondispose.__removeFuncList){
tgt.ondispose.push('ondispose.__removeFuncList handler',
callee.removeFuncList);
tgt.ondispose.__removeFuncList = [];
}
tgt.ondispose.__removeFuncList.push(memKey, pFunc);
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}/*installMethod*/;
vh.installMethod.installMethodArgcCheck = false;
/**
Installs methods into the given StructType-type object. Each
entry in the given methods object must map to a known member of
the given StructType, else an exception will be triggered.
See installMethod() for more details, including the semantics
of the 3rd argument.
On success, passes its first argument to holdRefence() and
returns this object. Throws on error.
*/
vh.installMethods = function(structType, methods,
applyArgcCheck=vh.installMethod.installMethodArgcCheck){
for(const k of Object.keys(methods)){
vh.installMethod(structType, k, methods[k], applyArgcCheck);
}
return vh.holdReference(structType);
};
/**
Uses sqlite3_vfs_register() to register the
sqlite3.capi.sqlite3_vfs-type vfs, which must have already been
filled out properly. If the 2nd argument is truthy, the VFS is
registered as the default VFS, else it is not.
On success, passes its first argument to this.holdReference() and
returns this object. Throws on error.
*/
vh.registerVfs = function(vfs, asDefault=false){
if(!(vfs instanceof sqlite3.capi.sqlite3_vfs)){
toss("Expecting a sqlite3_vfs-type argument.");
}
const rc = capi.sqlite3_vfs_register(vfs.pointer, asDefault ? 1 : 0);
if(rc){
toss("sqlite3_vfs_register(",vfs,") failed with rc",rc);
}
if(vfs.pointer !== capi.sqlite3_vfs_find(vfs.$zName)){
toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
vfs);
}
return vh.holdReference(vfs);
};
/**
A wrapper for installMethods() or registerVfs() to reduce
installation of a VFS and/or its I/O methods to a single
call.
Accepts an object which contains the properties "io" and/or
"vfs", each of which is itself an object with following properties:
- `struct`: an sqlite3.StructType-type struct. This must be a
populated (except for the methods) object of type
sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the
"vfs" entry).
- `methods`: an object mapping sqlite3_io_methods method names
(e.g. 'xClose') to JS implementations of those methods.
For each of those object, this function passes its (`struct`,
`methods`, (optional) `applyArgcCheck`) properties to
this.installMethods().
If the `vfs` entry is set then:
- Its `struct` property is passed to this.registerVfs(). The
`vfs` entry may optionally have an `asDefault` property, which
gets passed as the 2nd argument to registerVfs().
- If `struct.$zName` is falsy and the entry has a string-type
`name` property, `struct.$zName` is set to the C-string form of
that `name` value before registerVfs() is called.
On success returns this object. Throws on error.
*/
vh.installVfs = function(opt){
let count = 0;
const propList = ['io','vfs'];
for(const key of propList){
const o = opt[key];
if(o){
++count;
this.installMethods(o.struct, o.methods, !!o.applyArgcCheck);
if('vfs'===key){
if(!o.struct.$zName && 'string'===typeof o.name){
o.struct.$zName = wasm.allocCString(o.name);
/* Note that we leak that C-string. */
}
this.registerVfs(o.struct, !!o.asDefault);
}
}
}
if(!count) toss("Misuse: installVfs() options object requires at least",
"one of:", propList);
return this;
};
sqlite3.VfsHelper = vh;
}/*sqlite3ApiBootstrap.initializers.push()*/);

View File

@@ -678,7 +678,7 @@ const installOpfsVfs = function callee(options){
given pFile is open. given pFile is open.
*/ */
const f = __openFiles[pFile]; const f = __openFiles[pFile];
wasm.setMemValue(pOut, f.lockType ? 1 : 0, 'i32'); wasm.poke(pOut, f.lockType ? 1 : 0, 'i32');
return 0; return 0;
}, },
xClose: function(pFile){ xClose: function(pFile){
@@ -711,7 +711,7 @@ const installOpfsVfs = function callee(options){
if(0==rc){ if(0==rc){
try { try {
const sz = state.s11n.deserialize()[0]; const sz = state.s11n.deserialize()[0];
wasm.setMemValue(pSz64, sz, 'i64'); wasm.poke(pSz64, sz, 'i64');
}catch(e){ }catch(e){
error("Unexpected error reading xFileSize() result:",e); error("Unexpected error reading xFileSize() result:",e);
rc = state.sq3Codes.SQLITE_IOERR; rc = state.sq3Codes.SQLITE_IOERR;
@@ -803,27 +803,27 @@ const installOpfsVfs = function callee(options){
const vfsSyncWrappers = { const vfsSyncWrappers = {
xAccess: function(pVfs,zName,flags,pOut){ xAccess: function(pVfs,zName,flags,pOut){
mTimeStart('xAccess'); mTimeStart('xAccess');
const rc = opRun('xAccess', wasm.cstringToJs(zName)); const rc = opRun('xAccess', wasm.cstrToJs(zName));
wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' ); wasm.poke( pOut, (rc ? 0 : 1), 'i32' );
mTimeEnd(); mTimeEnd();
return 0; return 0;
}, },
xCurrentTime: function(pVfs,pOut){ xCurrentTime: function(pVfs,pOut){
/* If it turns out that we need to adjust for timezone, see: /* If it turns out that we need to adjust for timezone, see:
https://stackoverflow.com/a/11760121/1458521 */ https://stackoverflow.com/a/11760121/1458521 */
wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000), wasm.poke(pOut, 2440587.5 + (new Date().getTime()/86400000),
'double'); 'double');
return 0; return 0;
}, },
xCurrentTimeInt64: function(pVfs,pOut){ xCurrentTimeInt64: function(pVfs,pOut){
// TODO: confirm that this calculation is correct // TODO: confirm that this calculation is correct
wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(), wasm.poke(pOut, (2440587.5 * 86400000) + new Date().getTime(),
'i64'); 'i64');
return 0; return 0;
}, },
xDelete: function(pVfs, zName, doSyncDir){ xDelete: function(pVfs, zName, doSyncDir){
mTimeStart('xDelete'); mTimeStart('xDelete');
opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false); opRun('xDelete', wasm.cstrToJs(zName), doSyncDir, false);
/* We're ignoring errors because we cannot yet differentiate /* We're ignoring errors because we cannot yet differentiate
between harmless and non-harmless failures. */ between harmless and non-harmless failures. */
mTimeEnd(); mTimeEnd();
@@ -855,7 +855,7 @@ const installOpfsVfs = function callee(options){
C-string here. */ C-string here. */
opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP; opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP;
} }
zName = wasm.cstringToJs(zName); zName = wasm.cstrToJs(zName);
} }
const fh = Object.create(null); const fh = Object.create(null);
fh.fid = pFile; fh.fid = pFile;
@@ -867,7 +867,7 @@ const installOpfsVfs = function callee(options){
/* Recall that sqlite3_vfs::xClose() will be called, even on /* Recall that sqlite3_vfs::xClose() will be called, even on
error, unless pFile->pMethods is NULL. */ error, unless pFile->pMethods is NULL. */
if(fh.readOnly){ if(fh.readOnly){
wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32'); wasm.poke(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
} }
__openFiles[pFile] = fh; __openFiles[pFile] = fh;
fh.sabView = state.sabFileBufView; fh.sabView = state.sabFileBufView;
@@ -1156,11 +1156,8 @@ const installOpfsVfs = function callee(options){
opt.vfs = opfsVfs.$zName; opt.vfs = opfsVfs.$zName;
sqlite3.oo1.DB.dbCtorHelper.call(this, opt); sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
}; };
sqlite3.oo1.OpfsDb =
opfsUtil.OpfsDb /* sqlite3.opfs.OpfsDb => deprecated name -
will be phased out Real Soon */ =
OpfsDb;
OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype); OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
sqlite3.oo1.OpfsDb = OpfsDb;
sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql( sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql(
opfsVfs.pointer, opfsVfs.pointer,
function(oo1Db, sqlite3){ function(oo1Db, sqlite3){
@@ -1185,7 +1182,7 @@ const installOpfsVfs = function callee(options){
], 0, 0, 0); ], 0, 0, 0);
} }
); );
} }/*extend sqlite3.oo1*/
const sanityCheck = function(){ const sanityCheck = function(){
const scope = wasm.scopedAllocPush(); const scope = wasm.scopedAllocPush();
@@ -1205,7 +1202,7 @@ const installOpfsVfs = function callee(options){
log("deserialize() says:",rc); log("deserialize() says:",rc);
if("This is ä string."!==rc[0]) toss("String d13n error."); if("This is ä string."!==rc[0]) toss("String d13n error.");
vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
rc = wasm.getMemValue(pOut,'i32'); rc = wasm.peek(pOut,'i32');
log("xAccess(",dbFile,") exists ?=",rc); log("xAccess(",dbFile,") exists ?=",rc);
rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile, rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
fid, openFlags, pOut); fid, openFlags, pOut);
@@ -1216,22 +1213,22 @@ const installOpfsVfs = function callee(options){
return; return;
} }
vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
rc = wasm.getMemValue(pOut,'i32'); rc = wasm.peek(pOut,'i32');
if(!rc) toss("xAccess() failed to detect file."); if(!rc) toss("xAccess() failed to detect file.");
rc = ioSyncWrappers.xSync(sq3File.pointer, 0); rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
if(rc) toss('sync failed w/ rc',rc); if(rc) toss('sync failed w/ rc',rc);
rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024); rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
if(rc) toss('truncate failed w/ rc',rc); if(rc) toss('truncate failed w/ rc',rc);
wasm.setMemValue(pOut,0,'i64'); wasm.poke(pOut,0,'i64');
rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut); rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
if(rc) toss('xFileSize failed w/ rc',rc); if(rc) toss('xFileSize failed w/ rc',rc);
log("xFileSize says:",wasm.getMemValue(pOut, 'i64')); log("xFileSize says:",wasm.peek(pOut, 'i64'));
rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1); rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
if(rc) toss("xWrite() failed!"); if(rc) toss("xWrite() failed!");
const readBuf = wasm.scopedAlloc(16); const readBuf = wasm.scopedAlloc(16);
rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2); rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
wasm.setMemValue(readBuf+6,0); wasm.poke(readBuf+6,0);
let jRead = wasm.cstringToJs(readBuf); let jRead = wasm.cstrToJs(readBuf);
log("xRead() got:",jRead); log("xRead() got:",jRead);
if("sanity"!==jRead) toss("Unexpected xRead() value."); if("sanity"!==jRead) toss("Unexpected xRead() value.");
if(vfsSyncWrappers.xSleep){ if(vfsSyncWrappers.xSleep){
@@ -1244,7 +1241,7 @@ const installOpfsVfs = function callee(options){
log("Deleting file:",dbFile); log("Deleting file:",dbFile);
vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234); vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut); vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
rc = wasm.getMemValue(pOut,'i32'); rc = wasm.peek(pOut,'i32');
if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete()."); if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
warn("End of OPFS sanity checks."); warn("End of OPFS sanity checks.");
}finally{ }finally{
@@ -1271,7 +1268,7 @@ const installOpfsVfs = function callee(options){
and has finished initializing, so the real work can and has finished initializing, so the real work can
begin...*/ begin...*/
try { try {
sqlite3.VfsHelper.installVfs({ sqlite3.vfs.installVfs({
io: {struct: opfsIoMethods, methods: ioSyncWrappers}, io: {struct: opfsIoMethods, methods: ioSyncWrappers},
vfs: {struct: opfsVfs, methods: vfsSyncWrappers} vfs: {struct: opfsVfs, methods: vfsSyncWrappers}
}); });

View File

@@ -368,7 +368,7 @@ void sqlite3_wasm_test_struct(WasmTestStruct * s){
*/ */
SQLITE_WASM_KEEP SQLITE_WASM_KEEP
const char * sqlite3_wasm_enum_json(void){ const char * sqlite3_wasm_enum_json(void){
static char aBuffer[1024 * 12] = {0} /* where the JSON goes */; static char aBuffer[1024 * 20] = {0} /* where the JSON goes */;
int n = 0, nChildren = 0, nStruct = 0 int n = 0, nChildren = 0, nStruct = 0
/* output counters for figuring out where commas go */; /* output counters for figuring out where commas go */;
char * zPos = &aBuffer[1] /* skip first byte for now to help protect char * zPos = &aBuffer[1] /* skip first byte for now to help protect
@@ -410,6 +410,12 @@ const char * sqlite3_wasm_enum_json(void){
DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/; DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/;
} _DefGroup; } _DefGroup;
/* TODO? Authorizer... */
DefGroup(authorizer){
DefInt(SQLITE_DENY);
DefInt(SQLITE_IGNORE);
} _DefGroup;
DefGroup(blobFinalizers) { DefGroup(blobFinalizers) {
/* SQLITE_STATIC/TRANSIENT need to be handled explicitly as /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as
** integers to avoid casting-related warnings. */ ** integers to avoid casting-related warnings. */
@@ -424,6 +430,45 @@ const char * sqlite3_wasm_enum_json(void){
DefInt(SQLITE_NULL); DefInt(SQLITE_NULL);
} _DefGroup; } _DefGroup;
DefGroup(dbConfig){
DefInt(SQLITE_DBCONFIG_MAINDBNAME);
DefInt(SQLITE_DBCONFIG_LOOKASIDE);
DefInt(SQLITE_DBCONFIG_ENABLE_FKEY);
DefInt(SQLITE_DBCONFIG_ENABLE_TRIGGER);
DefInt(SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER);
DefInt(SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION);
DefInt(SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE);
DefInt(SQLITE_DBCONFIG_ENABLE_QPSG);
DefInt(SQLITE_DBCONFIG_TRIGGER_EQP);
DefInt(SQLITE_DBCONFIG_RESET_DATABASE);
DefInt(SQLITE_DBCONFIG_DEFENSIVE);
DefInt(SQLITE_DBCONFIG_WRITABLE_SCHEMA);
DefInt(SQLITE_DBCONFIG_LEGACY_ALTER_TABLE);
DefInt(SQLITE_DBCONFIG_DQS_DML);
DefInt(SQLITE_DBCONFIG_DQS_DDL);
DefInt(SQLITE_DBCONFIG_ENABLE_VIEW);
DefInt(SQLITE_DBCONFIG_LEGACY_FILE_FORMAT);
DefInt(SQLITE_DBCONFIG_TRUSTED_SCHEMA);
DefInt(SQLITE_DBCONFIG_MAX);
} _DefGroup;
DefGroup(dbStatus){
DefInt(SQLITE_DBSTATUS_LOOKASIDE_USED);
DefInt(SQLITE_DBSTATUS_CACHE_USED);
DefInt(SQLITE_DBSTATUS_SCHEMA_USED);
DefInt(SQLITE_DBSTATUS_STMT_USED);
DefInt(SQLITE_DBSTATUS_LOOKASIDE_HIT);
DefInt(SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE);
DefInt(SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL);
DefInt(SQLITE_DBSTATUS_CACHE_HIT);
DefInt(SQLITE_DBSTATUS_CACHE_MISS);
DefInt(SQLITE_DBSTATUS_CACHE_WRITE);
DefInt(SQLITE_DBSTATUS_DEFERRED_FKS);
DefInt(SQLITE_DBSTATUS_CACHE_USED_SHARED);
DefInt(SQLITE_DBSTATUS_CACHE_SPILL);
DefInt(SQLITE_DBSTATUS_MAX);
} _DefGroup;
DefGroup(encodings) { DefGroup(encodings) {
/* Noting that the wasm binding only aims to support UTF-8. */ /* Noting that the wasm binding only aims to support UTF-8. */
DefInt(SQLITE_UTF8); DefInt(SQLITE_UTF8);
@@ -505,6 +550,30 @@ const char * sqlite3_wasm_enum_json(void){
DefGroup(limits) { DefGroup(limits) {
DefInt(SQLITE_MAX_ALLOCATION_SIZE); DefInt(SQLITE_MAX_ALLOCATION_SIZE);
DefInt(SQLITE_LIMIT_LENGTH);
DefInt(SQLITE_MAX_LENGTH);
DefInt(SQLITE_LIMIT_SQL_LENGTH);
DefInt(SQLITE_MAX_SQL_LENGTH);
DefInt(SQLITE_LIMIT_COLUMN);
DefInt(SQLITE_MAX_COLUMN);
DefInt(SQLITE_LIMIT_EXPR_DEPTH);
DefInt(SQLITE_MAX_EXPR_DEPTH);
DefInt(SQLITE_LIMIT_COMPOUND_SELECT);
DefInt(SQLITE_MAX_COMPOUND_SELECT);
DefInt(SQLITE_LIMIT_VDBE_OP);
DefInt(SQLITE_MAX_VDBE_OP);
DefInt(SQLITE_LIMIT_FUNCTION_ARG);
DefInt(SQLITE_MAX_FUNCTION_ARG);
DefInt(SQLITE_LIMIT_ATTACHED);
DefInt(SQLITE_MAX_ATTACHED);
DefInt(SQLITE_LIMIT_LIKE_PATTERN_LENGTH);
DefInt(SQLITE_MAX_LIKE_PATTERN_LENGTH);
DefInt(SQLITE_LIMIT_VARIABLE_NUMBER);
DefInt(SQLITE_MAX_VARIABLE_NUMBER);
DefInt(SQLITE_LIMIT_TRIGGER_DEPTH);
DefInt(SQLITE_MAX_TRIGGER_DEPTH);
DefInt(SQLITE_LIMIT_WORKER_THREADS);
DefInt(SQLITE_MAX_WORKER_THREADS);
} _DefGroup; } _DefGroup;
DefGroup(openFlags) { DefGroup(openFlags) {
@@ -657,6 +726,31 @@ const char * sqlite3_wasm_enum_json(void){
DefInt(SQLITE_DESERIALIZE_RESIZEABLE); DefInt(SQLITE_DESERIALIZE_RESIZEABLE);
} _DefGroup; } _DefGroup;
DefGroup(sqlite3Status){
DefInt(SQLITE_STATUS_MEMORY_USED);
DefInt(SQLITE_STATUS_PAGECACHE_USED);
DefInt(SQLITE_STATUS_PAGECACHE_OVERFLOW);
//DefInt(SQLITE_STATUS_SCRATCH_USED) /* NOT USED */;
//DefInt(SQLITE_STATUS_SCRATCH_OVERFLOW) /* NOT USED */;
DefInt(SQLITE_STATUS_MALLOC_SIZE);
DefInt(SQLITE_STATUS_PARSER_STACK);
DefInt(SQLITE_STATUS_PAGECACHE_SIZE);
//DefInt(SQLITE_STATUS_SCRATCH_SIZE) /* NOT USED */;
DefInt(SQLITE_STATUS_MALLOC_COUNT);
} _DefGroup;
DefGroup(stmtStatus){
DefInt(SQLITE_STMTSTATUS_FULLSCAN_STEP);
DefInt(SQLITE_STMTSTATUS_SORT);
DefInt(SQLITE_STMTSTATUS_AUTOINDEX);
DefInt(SQLITE_STMTSTATUS_VM_STEP);
DefInt(SQLITE_STMTSTATUS_REPREPARE);
DefInt(SQLITE_STMTSTATUS_RUN);
DefInt(SQLITE_STMTSTATUS_FILTER_MISS);
DefInt(SQLITE_STMTSTATUS_FILTER_HIT);
DefInt(SQLITE_STMTSTATUS_MEMUSED);
} _DefGroup;
DefGroup(syncFlags) { DefGroup(syncFlags) {
DefInt(SQLITE_SYNC_NORMAL); DefInt(SQLITE_SYNC_NORMAL);
DefInt(SQLITE_SYNC_FULL); DefInt(SQLITE_SYNC_FULL);
@@ -670,6 +764,12 @@ const char * sqlite3_wasm_enum_json(void){
DefInt(SQLITE_TRACE_CLOSE); DefInt(SQLITE_TRACE_CLOSE);
} _DefGroup; } _DefGroup;
DefGroup(txnState){
DefInt(SQLITE_TXN_NONE);
DefInt(SQLITE_TXN_READ);
DefInt(SQLITE_TXN_WRITE);
} _DefGroup;
DefGroup(udfFlags) { DefGroup(udfFlags) {
DefInt(SQLITE_DETERMINISTIC); DefInt(SQLITE_DETERMINISTIC);
DefInt(SQLITE_DIRECTONLY); DefInt(SQLITE_DIRECTONLY);
@@ -681,7 +781,36 @@ const char * sqlite3_wasm_enum_json(void){
DefStr(SQLITE_VERSION); DefStr(SQLITE_VERSION);
DefStr(SQLITE_SOURCE_ID); DefStr(SQLITE_SOURCE_ID);
} _DefGroup; } _DefGroup;
DefGroup(vtab) {
DefInt(SQLITE_INDEX_SCAN_UNIQUE);
DefInt(SQLITE_INDEX_CONSTRAINT_EQ);
DefInt(SQLITE_INDEX_CONSTRAINT_GT);
DefInt(SQLITE_INDEX_CONSTRAINT_LE);
DefInt(SQLITE_INDEX_CONSTRAINT_LT);
DefInt(SQLITE_INDEX_CONSTRAINT_GE);
DefInt(SQLITE_INDEX_CONSTRAINT_MATCH);
DefInt(SQLITE_INDEX_CONSTRAINT_LIKE);
DefInt(SQLITE_INDEX_CONSTRAINT_GLOB);
DefInt(SQLITE_INDEX_CONSTRAINT_REGEXP);
DefInt(SQLITE_INDEX_CONSTRAINT_NE);
DefInt(SQLITE_INDEX_CONSTRAINT_ISNOT);
DefInt(SQLITE_INDEX_CONSTRAINT_ISNOTNULL);
DefInt(SQLITE_INDEX_CONSTRAINT_ISNULL);
DefInt(SQLITE_INDEX_CONSTRAINT_IS);
DefInt(SQLITE_INDEX_CONSTRAINT_LIMIT);
DefInt(SQLITE_INDEX_CONSTRAINT_OFFSET);
DefInt(SQLITE_INDEX_CONSTRAINT_FUNCTION);
DefInt(SQLITE_VTAB_CONSTRAINT_SUPPORT);
DefInt(SQLITE_VTAB_INNOCUOUS);
DefInt(SQLITE_VTAB_DIRECTONLY);
DefInt(SQLITE_ROLLBACK);
//DefInt(SQLITE_IGNORE); // Also used by sqlite3_authorizer() callback
DefInt(SQLITE_FAIL);
//DefInt(SQLITE_ABORT); // Also an error code
DefInt(SQLITE_REPLACE);
} _DefGroup;
#undef DefGroup #undef DefGroup
#undef DefStr #undef DefStr
#undef DefInt #undef DefInt
@@ -793,6 +922,128 @@ const char * sqlite3_wasm_enum_json(void){
} _StructBinder; } _StructBinder;
#undef CurrentStruct #undef CurrentStruct
#define CurrentStruct sqlite3_vtab
StructBinder {
M(pModule, "p");
M(nRef, "i");
M(zErrMsg, "p");
} _StructBinder;
#undef CurrentStruct
#define CurrentStruct sqlite3_vtab_cursor
StructBinder {
M(pVtab, "p");
} _StructBinder;
#undef CurrentStruct
#define CurrentStruct sqlite3_module
StructBinder {
M(iVersion, "i");
M(xCreate, "i(ppippp)");
M(xConnect, "i(ppippp)");
M(xBestIndex, "i(pp)");
M(xDisconnect, "i(p)");
M(xDestroy, "i(p)");
M(xOpen, "i(pp)");
M(xClose, "i(p)");
M(xFilter, "i(pisip)");
M(xNext, "i(p)");
M(xEof, "i(p)");
M(xColumn, "i(ppi)");
M(xRowid, "i(pp)");
M(xUpdate, "i(pipp)");
M(xBegin, "i(p)");
M(xSync, "i(p)");
M(xCommit, "i(p)");
M(xRollback, "i(p)");
M(xFindFunction, "i(pispp)");
M(xRename, "i(ps)");
// ^^^ v1. v2+ follows...
M(xSavepoint, "i(pi)");
M(xRelease, "i(pi)");
M(xRollbackTo, "i(pi)");
// ^^^ v2. v3+ follows...
M(xShadowName, "i(s)");
} _StructBinder;
#undef CurrentStruct
/**
** Workaround: in order to map the various inner structs from
** sqlite3_index_info, we have to uplift those into constructs we
** can access by type name. These structs _must_ match their
** in-sqlite3_index_info counterparts byte for byte.
*/
typedef struct {
int iColumn;
unsigned char op;
unsigned char usable;
int iTermOffset;
} sqlite3_index_constraint;
typedef struct {
int iColumn;
unsigned char desc;
} sqlite3_index_orderby;
typedef struct {
int argvIndex;
unsigned char omit;
} sqlite3_index_constraint_usage;
{ /* Validate that the above struct sizeof()s match
** expectations. We could improve upon this by
** checking the offsetof() for each member. */
const sqlite3_index_info siiCheck;
#define IndexSzCheck(T,M) \
(sizeof(T) == sizeof(*siiCheck.M))
if(!IndexSzCheck(sqlite3_index_constraint,aConstraint)
|| !IndexSzCheck(sqlite3_index_orderby,aOrderBy)
|| !IndexSzCheck(sqlite3_index_constraint_usage,aConstraintUsage)){
assert(!"sizeof mismatch in sqlite3_index_... struct(s)");
return 0;
}
#undef IndexSzCheck
}
#define CurrentStruct sqlite3_index_constraint
StructBinder {
M(iColumn, "i");
M(op, "C");
M(usable, "C");
M(iTermOffset, "i");
} _StructBinder;
#undef CurrentStruct
#define CurrentStruct sqlite3_index_orderby
StructBinder {
M(iColumn, "i");
M(desc, "C");
} _StructBinder;
#undef CurrentStruct
#define CurrentStruct sqlite3_index_constraint_usage
StructBinder {
M(argvIndex, "i");
M(omit, "C");
} _StructBinder;
#undef CurrentStruct
#define CurrentStruct sqlite3_index_info
StructBinder {
M(nConstraint, "i");
M(aConstraint, "p");
M(nOrderBy, "i");
M(aOrderBy, "p");
M(aConstraintUsage, "p");
M(idxNum, "i");
M(idxStr, "p");
M(needToFreeIdxStr, "i");
M(orderByConsumed, "i");
M(estimatedCost, "d");
M(estimatedRows, "j");
M(idxFlags, "i");
M(colUsed, "j");
} _StructBinder;
#undef CurrentStruct
#if SQLITE_WASM_TESTS #if SQLITE_WASM_TESTS
#define CurrentStruct WasmTestStruct #define CurrentStruct WasmTestStruct
StructBinder { StructBinder {
@@ -864,7 +1115,11 @@ sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
** **
** This function resets the given db pointer's database as described at ** This function resets the given db pointer's database as described at
** **
** https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigresetdatabase ** https://sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigresetdatabase
**
** But beware: virtual tables destroyed that way do not have their
** xDestroy() called, so will leak if they require that function for
** proper cleanup.
** **
** Returns 0 on success, an SQLITE_xxx code on error. Returns ** Returns 0 on success, an SQLITE_xxx code on error. Returns
** SQLITE_MISUSE if pDb is NULL. ** SQLITE_MISUSE if pDb is NULL.
@@ -873,6 +1128,7 @@ SQLITE_WASM_KEEP
int sqlite3_wasm_db_reset(sqlite3 *pDb){ int sqlite3_wasm_db_reset(sqlite3 *pDb){
int rc = SQLITE_MISUSE; int rc = SQLITE_MISUSE;
if( pDb ){ if( pDb ){
sqlite3_table_column_metadata(pDb, "main", 0, 0, 0, 0, 0, 0, 0);
rc = sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); rc = sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
if( 0==rc ){ if( 0==rc ){
rc = sqlite3_exec(pDb, "VACUUM", 0, 0, 0); rc = sqlite3_exec(pDb, "VACUUM", 0, 0, 0);
@@ -1109,6 +1365,93 @@ sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){
return &sqlite3KvvfsMethods; return &sqlite3KvvfsMethods;
} }
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
**
** This is a proxy for the variadic sqlite3_vtab_config() which passes
** its argument on, or not, to sqlite3_vtab_config(), depending on the
** value of its 2nd argument. Returns the result of
** sqlite3_vtab_config(), or SQLITE_MISUSE if the 2nd arg is not a
** valid value.
*/
SQLITE_WASM_KEEP
int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){
switch(op){
case SQLITE_VTAB_DIRECTONLY:
case SQLITE_VTAB_INNOCUOUS:
return sqlite3_vtab_config(pDb, op);
case SQLITE_VTAB_CONSTRAINT_SUPPORT:
return sqlite3_vtab_config(pDb, op, arg);
default:
return SQLITE_MISUSE;
}
}
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
**
** Wrapper for the variants of sqlite3_db_config() which take
** (int,int*) variadic args.
*/
SQLITE_WASM_KEEP
int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
switch(op){
case SQLITE_DBCONFIG_ENABLE_FKEY:
case SQLITE_DBCONFIG_ENABLE_TRIGGER:
case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
case SQLITE_DBCONFIG_ENABLE_QPSG:
case SQLITE_DBCONFIG_TRIGGER_EQP:
case SQLITE_DBCONFIG_RESET_DATABASE:
case SQLITE_DBCONFIG_DEFENSIVE:
case SQLITE_DBCONFIG_WRITABLE_SCHEMA:
case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE:
case SQLITE_DBCONFIG_DQS_DML:
case SQLITE_DBCONFIG_DQS_DDL:
case SQLITE_DBCONFIG_ENABLE_VIEW:
case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT:
case SQLITE_DBCONFIG_TRUSTED_SCHEMA:
return sqlite3_db_config(pDb, op, arg1, pArg2);
default: return SQLITE_MISUSE;
}
}
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
**
** Wrapper for the variants of sqlite3_db_config() which take
** (void*,int,int) variadic args.
*/
SQLITE_WASM_KEEP
int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){
switch(op){
case SQLITE_DBCONFIG_LOOKASIDE:
return sqlite3_db_config(pDb, op, pArg1, arg2, arg3);
default: return SQLITE_MISUSE;
}
}
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
**
** Wrapper for the variants of sqlite3_db_config() which take
** (const char *) variadic args.
*/
SQLITE_WASM_KEEP
int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
switch(op){
case SQLITE_DBCONFIG_MAINDBNAME:
return sqlite3_db_config(pDb, op, zArg);
default: return SQLITE_MISUSE;
}
}
#if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) #if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS)
#include <emscripten/wasmfs.h> #include <emscripten/wasmfs.h>

View File

@@ -227,20 +227,20 @@
const pSqlEnd = pSqlBegin + sqlByteLen; const pSqlEnd = pSqlBegin + sqlByteLen;
t = performance.now(); t = performance.now();
wasm.heap8().set(sql, pSql); wasm.heap8().set(sql, pSql);
wasm.setMemValue(pSql + sqlByteLen, 0); wasm.poke(pSql + sqlByteLen, 0);
metrics.strcpy = performance.now() - t; metrics.strcpy = performance.now() - t;
let breaker = 0; let breaker = 0;
while(pSql && wasm.getMemValue(pSql,'i8')){ while(pSql && wasm.peek(pSql,'i8')){
wasm.setPtrValue(ppStmt, 0); wasm.pokePtr(ppStmt, 0);
wasm.setPtrValue(pzTail, 0); wasm.pokePtr(pzTail, 0);
t = performance.now(); t = performance.now();
let rc = capi.sqlite3_prepare_v3( let rc = capi.sqlite3_prepare_v3(
db.handle, pSql, sqlByteLen, 0, ppStmt, pzTail db.handle, pSql, sqlByteLen, 0, ppStmt, pzTail
); );
metrics.prepTotal += performance.now() - t; metrics.prepTotal += performance.now() - t;
checkSqliteRc(db.handle, rc); checkSqliteRc(db.handle, rc);
pStmt = wasm.getPtrValue(ppStmt); pStmt = wasm.peekPtr(ppStmt);
pSql = wasm.getPtrValue(pzTail); pSql = wasm.peekPtr(pzTail);
sqlByteLen = pSqlEnd - pSql; sqlByteLen = pSqlEnd - pSql;
if(!pStmt) continue/*empty statement*/; if(!pStmt) continue/*empty statement*/;
++metrics.stmtCount; ++metrics.stmtCount;
@@ -495,7 +495,7 @@
const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
const ppDb = wasm.scopedAllocPtr(); const ppDb = wasm.scopedAllocPtr();
const rc = capi.sqlite3_open_v2(d.filename, ppDb, oFlags, null); const rc = capi.sqlite3_open_v2(d.filename, ppDb, oFlags, null);
pDb = wasm.getPtrValue(ppDb) pDb = wasm.peekPtr(ppDb)
if(rc) toss("sqlite3_open_v2() failed with code",rc); if(rc) toss("sqlite3_open_v2() failed with code",rc);
capi.sqlite3_exec(pDb, "PRAGMA cache_size="+cacheSize, 0, 0, 0); capi.sqlite3_exec(pDb, "PRAGMA cache_size="+cacheSize, 0, 0, 0);
this.logHtml(dbId,"cache_size =",cacheSize); this.logHtml(dbId,"cache_size =",cacheSize);

View File

@@ -1,4 +1,4 @@
/* emcscript-related styling, used during the module load/intialization processes... */ /* emscripten-related styling, used during the module load/intialization processes... */
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
div.emscripten { text-align: center; } div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; } div.emscripten_border { border: 1px solid black; }

View File

@@ -61,3 +61,9 @@ span.labeled-input {
flex-direction: column-reverse; flex-direction: column-reverse;
} }
label[for] { cursor: pointer } label[for] { cursor: pointer }
h1 {
border-radius: 0.25em;
padding: 0.15em 0.25em;
}
h1:first-of-type {margin: 0 0 0.5em 0;}

View File

@@ -316,13 +316,11 @@ self.WhWasmUtilInstaller = function(target){
Throws if passed an invalid n. Throws if passed an invalid n.
Pedantic side note: the name "heap" is a bit of a misnomer. In an Pedantic side note: the name "heap" is a bit of a misnomer. In a
Emscripten environment, the memory managed via the stack WASM environment, the stack and heap memory are all accessed via
allocation API is in the same Memory object as the heap (which the same view(s) of the memory.
makes sense because otherwise arbitrary pointer X would be
ambiguous: is it in the heap or the stack?).
*/ */
target.heapForSize = function(n,unsigned = false){ target.heapForSize = function(n,unsigned = true){
let ctor; let ctor;
const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
? cache : heapWrappers(); ? cache : heapWrappers();
@@ -496,6 +494,70 @@ self.WhWasmUtilInstaller = function(target){
e: { f: func } e: { f: func }
})).exports['f']; })).exports['f'];
}/*jsFuncToWasm()*/; }/*jsFuncToWasm()*/;
/**
Documented as target.installFunction() except for the 3rd
argument: if truthy, the newly-created function pointer
is stashed in the current scoped-alloc scope and will be
cleaned up at the matching scopedAllocPop(), else it
is not stashed there.
*/
const __installFunction = function f(func, sig, scoped){
if(scoped && !cache.scopedAlloc.length){
toss("No scopedAllocPush() scope is active.");
}
if('string'===typeof func){
const x = sig;
sig = func;
func = x;
}
if('string'!==typeof sig || !(func instanceof Function)){
toss("Invalid arguments: expecting (function,signature) "+
"or (signature,function).");
}
const ft = target.functionTable();
const oldLen = ft.length;
let ptr;
while(cache.freeFuncIndexes.length){
ptr = cache.freeFuncIndexes.pop();
if(ft.get(ptr)){ /* Table was modified via a different API */
ptr = null;
continue;
}else{
break;
}
}
if(!ptr){
ptr = oldLen;
ft.grow(1);
}
try{
/*this will only work if func is a WASM-exported function*/
ft.set(ptr, func);
if(scoped){
cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr);
}
return ptr;
}catch(e){
if(!(e instanceof TypeError)){
if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
throw e;
}
}
// It's not a WASM-exported function, so compile one...
try {
const fptr = target.jsFuncToWasm(func, sig);
ft.set(ptr, fptr);
if(scoped){
cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr);
}
}catch(e){
if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
throw e;
}
return ptr;
};
/** /**
Expects a JS function and signature, exactly as for Expects a JS function and signature, exactly as for
@@ -528,50 +590,19 @@ self.WhWasmUtilInstaller = function(target){
https://github.com/emscripten-core/emscripten/issues/17323 https://github.com/emscripten-core/emscripten/issues/17323
*/ */
target.installFunction = function f(func, sig){ target.installFunction = (func, sig)=>__installFunction(func, sig, false);
if(2!==arguments.length){
toss("installFunction() requires exactly 2 arguments"); /**
} EXPERIMENTAL! DO NOT USE IN CLIENT CODE!
if('string'===typeof func){
const x = sig; Works exactly like installFunction() but requires that a
sig = func; scopedAllocPush() is active and uninstalls the given function
func = x; when that alloc scope is popped via scopedAllocPop().
} This is used for implementing JS/WASM function bindings which
const ft = target.functionTable(); should only persist for the life of a call into a single
const oldLen = ft.length; C-side function.
let ptr; */
while(cache.freeFuncIndexes.length){ target.scopedInstallFunction = (func, sig)=>__installFunction(func, sig, true);
ptr = cache.freeFuncIndexes.pop();
if(ft.get(ptr)){ /* Table was modified via a different API */
ptr = null;
continue;
}else{
break;
}
}
if(!ptr){
ptr = oldLen;
ft.grow(1);
}
try{
/*this will only work if func is a WASM-exported function*/
ft.set(ptr, func);
return ptr;
}catch(e){
if(!(e instanceof TypeError)){
if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
throw e;
}
}
// It's not a WASM-exported function, so compile one...
try {
ft.set(ptr, target.jsFuncToWasm(func, sig));
}catch(e){
if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
throw e;
}
return ptr;
};
/** /**
Requires a pointer value previously returned from Requires a pointer value previously returned from
@@ -599,19 +630,24 @@ self.WhWasmUtilInstaller = function(target){
type triggers an exception if this.bigIntEnabled is type triggers an exception if this.bigIntEnabled is
falsy). Throws if given an invalid type. falsy). Throws if given an invalid type.
If the first argument is an array, it is treated as an array of
addresses and the result is an array of the values from each of
those address, using the same 2nd argument for determining the
value type to fetch.
As a special case, if type ends with a `*`, it is considered to As a special case, if type ends with a `*`, it is considered to
be a pointer type and is treated as the WASM numeric type be a pointer type and is treated as the WASM numeric type
appropriate for the pointer size (`i32`). appropriate for the pointer size (`i32`).
While likely not obvious, this routine and its setMemValue() While likely not obvious, this routine and its poke()
counterpart are how pointer-to-value _output_ parameters counterpart are how pointer-to-value _output_ parameters
in WASM-compiled C code can be interacted with: in WASM-compiled C code can be interacted with:
``` ```
const ptr = alloc(4); const ptr = alloc(4);
setMemValue(ptr, 0, 'i32'); // clear the ptr's value poke(ptr, 0, 'i32'); // clear the ptr's value
aCFuncWithOutputPtrToInt32Arg( ptr ); // e.g. void foo(int *x); aCFuncWithOutputPtrToInt32Arg( ptr ); // e.g. void foo(int *x);
const result = getMemValue(ptr, 'i32'); // fetch ptr's value const result = peek(ptr, 'i32'); // fetch ptr's value
dealloc(ptr); dealloc(ptr);
``` ```
@@ -623,15 +659,15 @@ self.WhWasmUtilInstaller = function(target){
const scope = scopedAllocPush(); const scope = scopedAllocPush();
try{ try{
const ptr = scopedAlloc(4); const ptr = scopedAlloc(4);
setMemValue(ptr, 0, 'i32'); poke(ptr, 0, 'i32');
aCFuncWithOutputPtrArg( ptr ); aCFuncWithOutputPtrArg( ptr );
result = getMemValue(ptr, 'i32'); result = peek(ptr, 'i32');
}finally{ }finally{
scopedAllocPop(scope); scopedAllocPop(scope);
} }
``` ```
As a rule setMemValue() must be called to set (typically zero As a rule poke() must be called to set (typically zero
out) the pointer's value, else it will contain an essentially out) the pointer's value, else it will contain an essentially
random value. random value.
@@ -639,70 +675,105 @@ self.WhWasmUtilInstaller = function(target){
painful impact on performance. Rather than doing so, use painful impact on performance. Rather than doing so, use
heapForSize() to fetch the heap object and read directly from it. heapForSize() to fetch the heap object and read directly from it.
See: setMemValue() See: poke()
*/ */
target.getMemValue = function(ptr, type='i8'){ target.peek = function f(ptr, type='i8'){
if(type.endsWith('*')) type = ptrIR; if(type.endsWith('*')) type = ptrIR;
const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
? cache : heapWrappers(); ? cache : heapWrappers();
switch(type){ const list = Array.isArray(ptr) ? [] : undefined;
case 'i1': let rc;
case 'i8': return c.HEAP8[ptr>>0]; do{
case 'i16': return c.HEAP16[ptr>>1]; if(list) ptr = arguments[0].shift();
case 'i32': return c.HEAP32[ptr>>2]; switch(type){
case 'i64': case 'i1':
if(target.bigIntEnabled) return BigInt(c.HEAP64[ptr>>3]); case 'i8': rc = c.HEAP8[ptr>>0]; break;
break; case 'i16': rc = c.HEAP16[ptr>>1]; break;
case 'float': case 'f32': return c.HEAP32F[ptr>>2]; case 'i32': rc = c.HEAP32[ptr>>2]; break;
case 'double': case 'f64': return Number(c.HEAP64F[ptr>>3]); case 'float': case 'f32': rc = c.HEAP32F[ptr>>2]; break;
default: break; case 'double': case 'f64': rc = Number(c.HEAP64F[ptr>>3]); break;
} case 'i64':
toss('Invalid type for getMemValue():',type); if(target.bigIntEnabled){
rc = BigInt(c.HEAP64[ptr>>3]);
break;
}
/* fallthru */
default:
toss('Invalid type for peek():',type);
}
if(list) list.push(rc);
}while(list && arguments[0].length);
return list || rc;
}; };
/** /**
The counterpart of getMemValue(), this sets a numeric value at The counterpart of peek(), this sets a numeric value at
the given WASM heap address, using the type to define how many the given WASM heap address, using the type to define how many
bytes are written. Throws if given an invalid type. See bytes are written. Throws if given an invalid type. See
getMemValue() for details about the type argument. If the 3rd peek() for details about the type argument. If the 3rd
argument ends with `*` then it is treated as a pointer type and argument ends with `*` then it is treated as a pointer type and
this function behaves as if the 3rd argument were `i32`. this function behaves as if the 3rd argument were `i32`.
This function returns itself. If the first argument is an array, it is treated like a list
of pointers and the given value is written to each one.
Returns `this`. (Prior to 2022-12-09 it returns this function.)
ACHTUNG: calling this often, e.g. in a loop, can have a noticably ACHTUNG: calling this often, e.g. in a loop, can have a noticably
painful impact on performance. Rather than doing so, use painful impact on performance. Rather than doing so, use
heapForSize() to fetch the heap object and assign directly to it. heapForSize() to fetch the heap object and assign directly to it
or use the heap's set() method.
*/ */
target.setMemValue = function f(ptr, value, type='i8'){ target.poke = function(ptr, value, type='i8'){
if (type.endsWith('*')) type = ptrIR; if (type.endsWith('*')) type = ptrIR;
const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength) const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
? cache : heapWrappers(); ? cache : heapWrappers();
switch (type) { for(const p of (Array.isArray(ptr) ? ptr : [ptr])){
case 'i1': switch (type) {
case 'i8': c.HEAP8[ptr>>0] = value; return f; case 'i1':
case 'i16': c.HEAP16[ptr>>1] = value; return f; case 'i8': c.HEAP8[p>>0] = value; continue;
case 'i32': c.HEAP32[ptr>>2] = value; return f; case 'i16': c.HEAP16[p>>1] = value; continue;
case 'i64': case 'i32': c.HEAP32[p>>2] = value; continue;
if(c.HEAP64){ case 'float': case 'f32': c.HEAP32F[p>>2] = value; continue;
c.HEAP64[ptr>>3] = BigInt(value); case 'double': case 'f64': c.HEAP64F[p>>3] = value; continue;
return f; case 'i64':
} if(c.HEAP64){
break; c.HEAP64[p>>3] = BigInt(value);
case 'float': case 'f32': c.HEAP32F[ptr>>2] = value; return f; continue;
case 'double': case 'f64': c.HEAP64F[ptr>>3] = value; return f; }
/* fallthru */
default:
toss('Invalid type for poke(): ' + type);
}
} }
toss('Invalid type for setMemValue(): ' + type); return this;
}; };
/**
Convenience form of peek() intended for fetching
pointer-to-pointer values. If passed a single non-array argument
it returns the value of that one pointer address. If passed
multiple arguments, or a single array of arguments, it returns an
array of their values.
*/
target.peekPtr = (...ptr)=>target.peek( (1===ptr.length ? ptr[0] : ptr), ptrIR );
/** Convenience form of getMemValue() intended for fetching /**
pointer-to-pointer values. */ A variant of poke() intended for setting
target.getPtrValue = (ptr)=>target.getMemValue(ptr, ptrIR); pointer-to-pointer values. Its differences from poke() are
that (1) it defaults to a value of 0, (2) it always writes
to the pointer-sized heap view, and (3) it returns `this`.
*/
target.pokePtr = (ptr, value=0)=>target.poke(ptr, value, ptrIR);
/** Convenience form of setMemValue() intended for setting /** Deprecated alias for getMemValue() */
pointer-to-pointer values. */ target.getMemValue = target.peek;
target.setPtrValue = (ptr, value)=>target.setMemValue(ptr, value, ptrIR); /** Deprecated alias for peekPtr() */
target.getPtrValue = target.peekPtr;
/** Deprecated alias for poke() */
target.setMemValue = target.poke;
/** Deprecated alias for pokePtr() */
target.setPtrValue = target.pokePtr;
/** /**
Returns true if the given value appears to be legal for use as Returns true if the given value appears to be legal for use as
@@ -725,11 +796,12 @@ self.WhWasmUtilInstaller = function(target){
Expects ptr to be a pointer into the WASM heap memory which Expects ptr to be a pointer into the WASM heap memory which
refers to a NUL-terminated C-style string encoded as UTF-8. refers to a NUL-terminated C-style string encoded as UTF-8.
Returns the length, in bytes, of the string, as for `strlen(3)`. Returns the length, in bytes, of the string, as for `strlen(3)`.
As a special case, if !ptr then it it returns `null`. Throws if As a special case, if !ptr or if it's not a pointer then it
ptr is out of range for target.heap8u(). returns `null`. Throws if ptr is out of range for
target.heap8u().
*/ */
target.cstrlen = function(ptr){ target.cstrlen = function(ptr){
if(!ptr) return null; if(!ptr || !target.isPtr(ptr)) return null;
const h = heapWrappers().HEAP8U; const h = heapWrappers().HEAP8U;
let pos = ptr; let pos = ptr;
for( ; h[pos] !== 0; ++pos ){} for( ; h[pos] !== 0; ++pos ){}
@@ -753,9 +825,9 @@ self.WhWasmUtilInstaller = function(target){
refers to a NUL-terminated C-style string encoded as UTF-8. This refers to a NUL-terminated C-style string encoded as UTF-8. This
function counts its byte length using cstrlen() then returns a function counts its byte length using cstrlen() then returns a
JS-format string representing its contents. As a special case, if JS-format string representing its contents. As a special case, if
ptr is falsy, `null` is returned. ptr is falsy or not a pointer, `null` is returned.
*/ */
target.cstringToJs = function(ptr){ target.cstrToJs = function(ptr){
const n = target.cstrlen(ptr); const n = target.cstrlen(ptr);
return n ? __utf8Decode(heapWrappers().HEAP8U, ptr, ptr+n) : (null===n ? n : ""); return n ? __utf8Decode(heapWrappers().HEAP8U, ptr, ptr+n) : (null===n ? n : "");
}; };
@@ -942,10 +1014,19 @@ self.WhWasmUtilInstaller = function(target){
const __allocCStr = function(jstr, returnWithLength, allocator, funcName){ const __allocCStr = function(jstr, returnWithLength, allocator, funcName){
__affirmAlloc(target, funcName); __affirmAlloc(target, funcName);
if('string'!==typeof jstr) return null; if('string'!==typeof jstr) return null;
const n = target.jstrlen(jstr), if(0){/* older impl, possibly more widely compatible? */
ptr = allocator(n+1); const n = target.jstrlen(jstr),
target.jstrcpy(jstr, target.heap8u(), ptr, n+1, true); ptr = allocator(n+1);
return returnWithLength ? [ptr, n] : ptr; target.jstrcpy(jstr, target.heap8u(), ptr, n+1, true);
return returnWithLength ? [ptr, n] : ptr;
}else{/* newer, (probably) faster and (certainly) simpler impl */
const u = cache.utf8Encoder.encode(jstr),
ptr = allocator(u.length+1),
heap = heapWrappers().HEAP8U;
heap.set(u, ptr);
heap[ptr + u.length] = 0;
return returnWithLength ? [ptr, u.length] : ptr;
}
}; };
/** /**
@@ -1035,7 +1116,13 @@ self.WhWasmUtilInstaller = function(target){
if(n<0) toss("Invalid state object for scopedAllocPop()."); if(n<0) toss("Invalid state object for scopedAllocPop().");
if(0===arguments.length) state = cache.scopedAlloc[n]; if(0===arguments.length) state = cache.scopedAlloc[n];
cache.scopedAlloc.splice(n,1); cache.scopedAlloc.splice(n,1);
for(let p; (p = state.pop()); ) target.dealloc(p); for(let p; (p = state.pop()); ){
if(target.functionEntry(p)){
//console.warn("scopedAllocPop() uninstalling transient function",p);
target.uninstallFunction(p);
}
else target.dealloc(p);
}
}; };
/** /**
@@ -1081,40 +1168,67 @@ self.WhWasmUtilInstaller = function(target){
// impl for allocMainArgv() and scopedAllocMainArgv(). // impl for allocMainArgv() and scopedAllocMainArgv().
const __allocMainArgv = function(isScoped, list){ const __allocMainArgv = function(isScoped, list){
if(!list.length) toss("Cannot allocate empty array.");
const pList = target[ const pList = target[
isScoped ? 'scopedAlloc' : 'alloc' isScoped ? 'scopedAlloc' : 'alloc'
](list.length * target.ptrSizeof); ]((list.length + 1) * target.ptrSizeof);
let i = 0; let i = 0;
list.forEach((e)=>{ list.forEach((e)=>{
target.setPtrValue(pList + (target.ptrSizeof * i++), target.pokePtr(pList + (target.ptrSizeof * i++),
target[ target[
isScoped ? 'scopedAllocCString' : 'allocCString' isScoped ? 'scopedAllocCString' : 'allocCString'
](""+e)); ](""+e));
}); });
target.pokePtr(pList + (target.ptrSizeof * i), 0);
return pList; return pList;
}; };
/** /**
Creates an array, using scopedAlloc(), suitable for passing to a Creates an array, using scopedAlloc(), suitable for passing to a
C-level main() routine. The input is a collection with a length C-level main() routine. The input is a collection with a length
property and a forEach() method. A block of memory list.length property and a forEach() method. A block of memory
entries long is allocated and each pointer-sized block of that (list.length+1) entries long is allocated and each pointer-sized
memory is populated with a scopedAllocCString() conversion of the block of that memory is populated with a scopedAllocCString()
(""+value) of each element. Returns a pointer to the start of the conversion of the (""+value) of each element, with the exception
list, suitable for passing as the 2nd argument to a C-style that the final entry is a NULL pointer. Returns a pointer to the
main() function. start of the list, suitable for passing as the 2nd argument to a
C-style main() function.
Throws if list.length is falsy or scopedAllocPush() is not active. Throws if scopedAllocPush() is not active.
Design note: the returned array is allocated with an extra NULL
pointer entry to accommodate certain APIs, but client code which
does not need that functionality should treat the returned array
as list.length entries long.
*/ */
target.scopedAllocMainArgv = (list)=>__allocMainArgv(true, list); target.scopedAllocMainArgv = (list)=>__allocMainArgv(true, list);
/** /**
Identical to scopedAllocMainArgv() but uses alloc() instead of Identical to scopedAllocMainArgv() but uses alloc() instead of
scopedAllocMainArgv scopedAlloc().
*/ */
target.allocMainArgv = (list)=>__allocMainArgv(false, list); target.allocMainArgv = (list)=>__allocMainArgv(false, list);
/**
Expects to be given a C-style string array and its length. It
returns a JS array of strings and/or nulls: any entry in the
pArgv array which is NULL results in a null entry in the result
array. If argc is 0 then an empty array is returned.
Results are undefined if any entry in the first argc entries of
pArgv are neither 0 (NULL) nor legal UTF-format C strings.
To be clear, the expected C-style arguments to be passed to this
function are `(int, char **)` (optionally const-qualified).
*/
target.cArgvToJs = (argc, pArgv)=>{
const list = [];
for(let i = 0; i < argc; ++i){
const arg = target.peekPtr(pArgv + (target.ptrSizeof * i));
list.push( arg ? target.cstrToJs(arg) : null );
}
return list;
};
/** /**
Wraps function call func() in a scopedAllocPush() and Wraps function call func() in a scopedAllocPush() and
scopedAllocPop() block, such that all calls to scopedAlloc() and scopedAllocPop() block, such that all calls to scopedAlloc() and
@@ -1133,7 +1247,7 @@ self.WhWasmUtilInstaller = function(target){
__affirmAlloc(target, method); __affirmAlloc(target, method);
const pIr = safePtrSize ? 'i64' : ptrIR; const pIr = safePtrSize ? 'i64' : ptrIR;
let m = target[method](howMany * (safePtrSize ? 8 : ptrSizeof)); let m = target[method](howMany * (safePtrSize ? 8 : ptrSizeof));
target.setMemValue(m, 0, pIr) target.poke(m, 0, pIr)
if(1===howMany){ if(1===howMany){
return m; return m;
} }
@@ -1141,7 +1255,7 @@ self.WhWasmUtilInstaller = function(target){
for(let i = 1; i < howMany; ++i){ for(let i = 1; i < howMany; ++i){
m += (safePtrSize ? 8 : ptrSizeof); m += (safePtrSize ? 8 : ptrSizeof);
a[i] = m; a[i] = m;
target.setMemValue(m, 0, pIr); target.poke(m, 0, pIr);
} }
return a; return a;
}; };
@@ -1172,7 +1286,7 @@ self.WhWasmUtilInstaller = function(target){
When one of the returned pointers will refer to a 64-bit value, When one of the returned pointers will refer to a 64-bit value,
e.g. a double or int64, an that value must be written or fetched, e.g. a double or int64, an that value must be written or fetched,
e.g. using setMemValue() or getMemValue(), it is important that e.g. using poke() or peek(), it is important that
the pointer in question be aligned to an 8-byte boundary or else the pointer in question be aligned to an 8-byte boundary or else
it will not be fetched or written properly and will corrupt or it will not be fetched or written properly and will corrupt or
read neighboring memory. It is only safe to pass false when the read neighboring memory. It is only safe to pass false when the
@@ -1228,32 +1342,39 @@ self.WhWasmUtilInstaller = function(target){
State for use with xWrap() State for use with xWrap()
*/ */
cache.xWrap = Object.create(null); cache.xWrap = Object.create(null);
const xcv = cache.xWrap.convert = Object.create(null); cache.xWrap.convert = Object.create(null);
/** Map of type names to argument conversion functions. */ /** Map of type names to argument conversion functions. */
cache.xWrap.convert.arg = Object.create(null); cache.xWrap.convert.arg = new Map;
/** Map of type names to return result conversion functions. */ /** Map of type names to return result conversion functions. */
cache.xWrap.convert.result = Object.create(null); cache.xWrap.convert.result = new Map;
const xArg = cache.xWrap.convert.arg, xResult = cache.xWrap.convert.result;
if(target.bigIntEnabled){ if(target.bigIntEnabled){
xcv.arg.i64 = (i)=>BigInt(i); xArg.set('i64', (i)=>BigInt(i));
} }
xcv.arg.i32 = (i)=>(i | 0); xArg.set('i32', (i)=>(i | 0));
xcv.arg.i16 = (i)=>((i | 0) & 0xFFFF); xArg.set('i16', (i)=>((i | 0) & 0xFFFF));
xcv.arg.i8 = (i)=>((i | 0) & 0xFF); xArg.set('i8', (i)=>((i | 0) & 0xFF));
xcv.arg.f32 = xcv.arg.float = (i)=>Number(i).valueOf(); xArg.set('f32', (i)=>Number(i).valueOf());
xcv.arg.f64 = xcv.arg.double = xcv.arg.f32; xArg.set('float', xArg.get('f32'));
xcv.arg.int = xcv.arg.i32; xArg.set('f64', xArg.get('f32'));
xcv.result['*'] = xcv.result['pointer'] = xcv.arg['**'] = xcv.arg[ptrIR]; xArg.set('double', xArg.get('f64'));
xcv.result['number'] = (v)=>Number(v); xArg.set('int', xArg.get('i32'));
xResult.set('*', xArg.get(ptrIR));
xResult.set('pointer', xArg.get(ptrIR));
xArg.set('**', xArg.get(ptrIR));
xResult.set('number', (v)=>Number(v));
{ /* Copy certain xcv.arg[...] handlers to xcv.result[...] and { /* Copy certain xArg[...] handlers to xResult[...] and
add pointer-style variants of them. */ add pointer-style variants of them. */
const copyToResult = ['i8', 'i16', 'i32', 'int', const copyToResult = ['i8', 'i16', 'i32', 'int',
'f32', 'float', 'f64', 'double']; 'f32', 'float', 'f64', 'double'];
if(target.bigIntEnabled) copyToResult.push('i64'); if(target.bigIntEnabled) copyToResult.push('i64');
const adaptPtr = xArg.get(ptrIR);
for(const t of copyToResult){ for(const t of copyToResult){
xcv.arg[t+'*'] = xcv.result[t+'*'] = xcv.arg[ptrIR]; xArg.set(t+'*', adaptPtr);
xcv.result[t] = xcv.arg[t] || toss("Missing arg converter:",t); xResult.set(t+'*', adaptPtr);
xResult.set(t, (xArg.get(t) || toss("Missing arg converter:",t)));
} }
} }
@@ -1273,23 +1394,28 @@ self.WhWasmUtilInstaller = function(target){
Would that be too much magic concentrated in one place, ready to Would that be too much magic concentrated in one place, ready to
backfire? backfire?
*/ */
xcv.arg.string = xcv.arg.utf8 = xcv.arg['pointer'] = xcv.arg['*'] xArg.set('string', function(v){
= function(v){ if('string'===typeof v) return target.scopedAllocCString(v);
if('string'===typeof v) return target.scopedAllocCString(v); return v ? xArg.get(ptrIR)(v) : null;
return v ? xcv.arg[ptrIR](v) : null; });
}; xArg.set('utf8', xArg.get('string'));
xcv.result.string = xcv.result.utf8 = (i)=>target.cstringToJs(i); xArg.set('pointer', xArg.get('string'));
xcv.result['string:dealloc'] = xcv.result['utf8:dealloc'] = (i)=>{ xArg.set('*', xArg.get('string'));
try { return i ? target.cstringToJs(i) : null }
xResult.set('string', (i)=>target.cstrToJs(i));
xResult.set('utf8', xResult.get('string'));
xResult.set('string:dealloc', (i)=>{
try { return i ? target.cstrToJs(i) : null }
finally{ target.dealloc(i) } finally{ target.dealloc(i) }
}; });
xcv.result.json = (i)=>JSON.parse(target.cstringToJs(i)); xResult.set('utf8:dealloc', xResult.get('string:dealloc'));
xcv.result['json:dealloc'] = (i)=>{ xResult.set('json', (i)=>JSON.parse(target.cstrToJs(i)));
try{ return i ? JSON.parse(target.cstringToJs(i)) : null } xResult.set('json:dealloc', (i)=>{
try{ return i ? JSON.parse(target.cstrToJs(i)) : null }
finally{ target.dealloc(i) } finally{ target.dealloc(i) }
} });
xcv.result['void'] = (v)=>undefined; xResult.set('void', (v)=>undefined);
xcv.result['null'] = (v)=>v; xResult.set('null', (v)=>v);
if(0){ if(0){
/*** /***
@@ -1302,19 +1428,178 @@ self.WhWasmUtilInstaller = function(target){
the value will always be treated like -1 (which has a useful the value will always be treated like -1 (which has a useful
case in the sqlite3 bindings). case in the sqlite3 bindings).
*/ */
xcv.arg['func-ptr'] = function(v){ xArg.set('func-ptr', function(v){
if(!(v instanceof Function)) return xcv.arg[ptrIR]; if(!(v instanceof Function)) return xArg.get(ptrIR);
const f = target.jsFuncToWasm(v, WHAT_SIGNATURE); const f = target.jsFuncToWasm(v, WHAT_SIGNATURE);
}; });
} }
/**
EXPERIMENTAL! DO NOT USE IN CLIENT CODE!
An attempt at adding function pointer conversion support to
xWrap(). This type is recognized by xWrap() as a proxy for
converting a JS function to a C-side function, either
permanently, for the duration of a single call into the C layer,
or semi-contextual, where it may keep track of a single binding
for a given context and uninstall the binding if it's replaced.
Requires an options object with these properties:
- name (optional): string describing the function binding. This
is solely for debugging and error-reporting purposes. If not
provided, an empty string is assumed.
- signature: an function signature compatible with
jsFuncToWasm().
- bindScope (string): one of ('transient', 'context',
'singleton'). Bind scopes are:
- transient: it will convert JS functions to WASM only for the
duration of the xWrap()'d function call, using
scopedInstallFunction(). Before that call returns, the
WASM-side binding will be uninstalled.
- singleton: holds one function-pointer binding for this
instance. If it's called with a different function pointer,
it uninstalls the previous one after converting the new
value. This is only useful for use with "global" functions
which do not rely on any state other than this function
pointer. If the being-converted function pointer is intended
to be mapped to some sort of state object (e.g. an sqlite3*)
then "context" (see below) is the proper mode.
- context: similar to singleton mode but for a given "context",
where the context is a key provided by the user and possibly
dependent on a small amount of call-time context. This mode
is the default if bindScope is _not_ set but a property named
contextKey (described below) is.
FIXME: the contextKey definition is only useful for very basic
contexts and breaks down with dynamic ones like
sqlite3_create_collation().
- contextKey (function): only used if bindScope is not set or is
'context'. This function gets passed (argIndex,argv), where
argIndex is the index of this function pointer in its
_wrapping_ function's arguments and argv is the _current_
being-xWrap()-processed args array. All arguments to the left
of argIndex will have been processed by xWrap() by the time
this is called. argv[argIndex] will be the value the user
passed in to the xWrap()'d function for the argument this
FuncPtrAdapter is mapped to. Arguments to the right of
argv[argIndex] will not yet have been converted before this is
called. The function must return a key which uniquely
identifies this function mapping context for _this_
FuncPtrAdapter instance (other instances are not considered),
taking into account that C functions often take some sort of
state object as one or more of their arguments. As an example,
if the xWrap()'d function takes `(int,T*,functionPtr,X*)` and
this FuncPtrAdapter is the argv[2]nd arg, contextKey(2,argv)
might return 'T@'+argv[1], or even just argv[1]. Note,
however, that the (X*) argument will not yet have been
processed by the time this is called and should not be used as
part of that key. Similarly, C-string-type keys should not be
used as part of keys because they are normally transient in
this environment.
The constructor only saves the above state for later, and does
not actually bind any functions. Its convertArg() methor is
called via xWrap() to perform any bindings.
Caveats:
- singleton is globally singleton. This type does not currently
have enough context to apply, e.g., a different singleton for
each (sqlite3*) db handle.
*/
xArg.FuncPtrAdapter = function ctor(opt) {
if(!(this instanceof xArg.FuncPtrAdapter)){
toss("FuncPtrAdapter can only be used as a constructor. Use 'new'.");
}
this.signature = opt.signature;
if(!opt.bindScope && (opt.contextKey instanceof Function)){
opt.bindScope = 'context';
}else if(ctor.bindScopes.indexOf(opt.bindScope)<0){
toss("Invalid options.bindScope ("+opt.bindMod+") for FuncPtrAdapter. "+
"Expecting one of: ("+ctor.bindScopes.join(', ')+')');
}
this.bindScope = opt.bindScope;
this.name = opt.name || '';
if(opt.contextKey) this.contextKey = opt.contextKey /*else inherit one*/;
this.isTransient = 'transient'===this.bindScope;
this.isContext = 'context'===this.bindScope;
if( ('singleton'===this.bindScope) ){
this.singleton = [];
}else{
this.singleton = undefined;
}
//console.warn("FuncPtrAdapter()",opt,this);
};
xArg.FuncPtrAdapter.bindScopes = [
'transient', 'context', 'singleton'
];
xArg.FuncPtrAdapter.prototype = {
contextKey: function(argIndex,argv){
return this;
},
contextMap: function(key){
const cm = (this.__cmap || (this.__cmap = new Map));
let rc = cm.get(key);
if(undefined===rc) cm.set(key, (rc = []));
return rc;
},
/**
Gets called via xWrap() to "convert" v to a WASM-bound function
pointer. If v is one of (a pointer, null, undefined) then
(v||0) is returned, otherwise v must be a Function, for which
it creates (if needed) a WASM function binding and returns the
WASM pointer to that binding. It will remember the binding for
at least the next call, to avoid recreating the function
unnecessarily.
*/
convertArg: function(v,argIndex,argv){
//console.warn("FuncPtrAdapter.convertArg()",this.signature,this.transient,v);
let pair = this.singleton;
if(!pair && this.isContext){
pair = this.contextMap(this.contextKey(argIndex, argv));
}
if(pair && pair[0]===v) return pair[1];
if(v instanceof Function){
const fp = __installFunction(v, this.signature, this.isTransient);
if(pair){
if(pair[1]){
try{target.uninstallFunction(pair[1])}
catch(e){/*ignored*/}
}
pair[0] = v;
pair[1] = fp;
}
return fp;
}else if(target.isPtr(v) || null===v || undefined===v){
if(pair && pair[1]){
try{target.uninstallFunction(pair[1])}
catch(e){/*ignored*/}
pair[0] = pair[1] = (v || 0);
}
return v || 0;
}else{
throw new TypeError("Invalid FuncPtrAdapter argument type. "+
"Expecting "+(this.name ? this.name+' ' : '')+
"function matching signature "+
this.signature+".");
}
}
}/*FuncPtrAdapter.prototype*/;
const __xArgAdapterCheck = const __xArgAdapterCheck =
(t)=>xcv.arg[t] || toss("Argument adapter not found:",t); (t)=>xArg.get(t) || toss("Argument adapter not found:",t);
const __xResultAdapterCheck = const __xResultAdapterCheck =
(t)=>xcv.result[t] || toss("Result adapter not found:",t); (t)=>xResult.get(t) || toss("Result adapter not found:",t);
cache.xWrap.convertArg = (t,v)=>__xArgAdapterCheck(t)(v); cache.xWrap.convertArg = (t,...args)=>__xArgAdapterCheck(t)(...args);
cache.xWrap.convertResult = cache.xWrap.convertResult =
(t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined)); (t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined));
@@ -1423,7 +1708,7 @@ self.WhWasmUtilInstaller = function(target){
```js ```js
target.xWrap.resultAdapter('string:my_free',(i)=>{ target.xWrap.resultAdapter('string:my_free',(i)=>{
try { return i ? target.cstringToJs(i) : null } try { return i ? target.cstrToJs(i) : null }
finally{ target.exports.my_free(i) } finally{ target.exports.my_free(i) }
}; };
``` ```
@@ -1451,8 +1736,8 @@ self.WhWasmUtilInstaller = function(target){
- Figure out how/whether we can (semi-)transparently handle - Figure out how/whether we can (semi-)transparently handle
pointer-type _output_ arguments. Those currently require pointer-type _output_ arguments. Those currently require
explicit handling by allocating pointers, assigning them before explicit handling by allocating pointers, assigning them before
the call using setMemValue(), and fetching them with the call using poke(), and fetching them with
getMemValue() after the call. We may be able to automate some peek() after the call. We may be able to automate some
or all of that. or all of that.
- Figure out whether it makes sense to extend the arg adapter - Figure out whether it makes sense to extend the arg adapter
@@ -1478,19 +1763,23 @@ self.WhWasmUtilInstaller = function(target){
} }
/*Verify the arg type conversions are valid...*/; /*Verify the arg type conversions are valid...*/;
if(undefined!==resultType && null!==resultType) __xResultAdapterCheck(resultType); if(undefined!==resultType && null!==resultType) __xResultAdapterCheck(resultType);
argTypes.forEach(__xArgAdapterCheck); for(const t of argTypes){
if(t instanceof xArg.FuncPtrAdapter) xArg.set(t, (...args)=>t.convertArg(...args));
else __xArgAdapterCheck(t);
}
const cxw = cache.xWrap;
if(0===xf.length){ if(0===xf.length){
// No args to convert, so we can create a simpler wrapper... // No args to convert, so we can create a simpler wrapper...
return (...args)=>(args.length return (...args)=>(args.length
? __argcMismatch(fname, xf.length) ? __argcMismatch(fname, xf.length)
: cache.xWrap.convertResult(resultType, xf.call(null))); : cxw.convertResult(resultType, xf.call(null)));
} }
return function(...args){ return function(...args){
if(args.length!==xf.length) __argcMismatch(fname, xf.length); if(args.length!==xf.length) __argcMismatch(fname, xf.length);
const scope = target.scopedAllocPush(); const scope = target.scopedAllocPush();
try{ try{
const rc = xf.apply(null,args.map((v,i)=>cache.xWrap.convertArg(argTypes[i], v))); for(const i in args) args[i] = cxw.convertArg(argTypes[i], args[i], i, args);
return cache.xWrap.convertResult(resultType, rc); return cxw.convertResult(resultType, xf.apply(null,args));
}finally{ }finally{
target.scopedAllocPop(scope); target.scopedAllocPop(scope);
} }
@@ -1500,15 +1789,15 @@ self.WhWasmUtilInstaller = function(target){
/** Internal impl for xWrap.resultAdapter() and argAdapter(). */ /** Internal impl for xWrap.resultAdapter() and argAdapter(). */
const __xAdapter = function(func, argc, typeName, adapter, modeName, xcvPart){ const __xAdapter = function(func, argc, typeName, adapter, modeName, xcvPart){
if('string'===typeof typeName){ if('string'===typeof typeName){
if(1===argc) return xcvPart[typeName]; if(1===argc) return xcvPart.get(typeName);
else if(2===argc){ else if(2===argc){
if(!adapter){ if(!adapter){
delete xcvPart[typeName]; delete xcvPart.get(typeName);
return func; return func;
}else if(!(adapter instanceof Function)){ }else if(!(adapter instanceof Function)){
toss(modeName,"requires a function argument."); toss(modeName,"requires a function argument.");
} }
xcvPart[typeName] = adapter; xcvPart.set(typeName, adapter);
return func; return func;
} }
} }
@@ -1545,7 +1834,7 @@ self.WhWasmUtilInstaller = function(target){
*/ */
target.xWrap.resultAdapter = function f(typeName, adapter){ target.xWrap.resultAdapter = function f(typeName, adapter){
return __xAdapter(f, arguments.length, typeName, adapter, return __xAdapter(f, arguments.length, typeName, adapter,
'resultAdapter()', xcv.result); 'resultAdapter()', xResult);
}; };
/** /**
@@ -1575,9 +1864,11 @@ self.WhWasmUtilInstaller = function(target){
*/ */
target.xWrap.argAdapter = function f(typeName, adapter){ target.xWrap.argAdapter = function f(typeName, adapter){
return __xAdapter(f, arguments.length, typeName, adapter, return __xAdapter(f, arguments.length, typeName, adapter,
'argAdapter()', xcv.arg); 'argAdapter()', xArg);
}; };
target.xWrap.FuncPtrAdapter = xArg.FuncPtrAdapter;
/** /**
Functions like xCall() but performs argument and result type Functions like xCall() but performs argument and result type
conversions as for xWrap(). The first argument is the name of the conversions as for xWrap(). The first argument is the name of the

View File

@@ -38,7 +38,7 @@ dist-name := $(dist-name-prefix)-TEMP
# date. Our general policy is that we want the smallest binaries for # date. Our general policy is that we want the smallest binaries for
# dist zip files, so use the oz build unless there is a compelling # dist zip files, so use the oz build unless there is a compelling
# reason not to. # reason not to.
dist.build ?= oz dist.build ?= qoz
dist-dir.top := $(dist-name) dist-dir.top := $(dist-name)
dist-dir.jswasm := $(dist-dir.top)/$(notdir $(dir.dout)) dist-dir.jswasm := $(dist-dir.top)/$(notdir $(dir.dout))
@@ -63,7 +63,7 @@ dist.common.extras := \
# $(dist.build) will depend on clean, having any deps on # $(dist.build) will depend on clean, having any deps on
# $(dist-archive) which themselves may be cleaned up by the clean # $(dist-archive) which themselves may be cleaned up by the clean
# target will lead to grief in parallel builds (-j #). Thus # target will lead to grief in parallel builds (-j #). Thus
# $(dist-target)'s deps must be trimmed to non-generated files or # dist's deps must be trimmed to non-generated files or
# files which are _not_ cleaned up by the clean target. # files which are _not_ cleaned up by the clean target.
# #
# Note that we require $(bin.version-info) in order to figure out the # Note that we require $(bin.version-info) in order to figure out the

View File

@@ -10,9 +10,12 @@
*********************************************************************** ***********************************************************************
The Jaccwabyt API is documented in detail in an external file. The Jaccwabyt API is documented in detail in an external file,
_possibly_ called jaccwabyt.md in the same directory as this file.
Project home: https://fossil.wanderinghorse.net/r/jaccwabyt Project homes:
- https://fossil.wanderinghorse.net/r/jaccwabyt
- https://sqlite.org/src/dir/ext/wasm/jaccwabyt
*/ */
'use strict'; 'use strict';
@@ -61,7 +64,6 @@ self.Jaccwabyt = function StructBinderFactory(config){
BigInt = self['BigInt'], BigInt = self['BigInt'],
BigInt64Array = self['BigInt64Array'], BigInt64Array = self['BigInt64Array'],
/* Undocumented (on purpose) config options: */ /* Undocumented (on purpose) config options: */
functionTable = config.functionTable/*EXPERIMENTAL, undocumented*/,
ptrSizeof = config.ptrSizeof || 4, ptrSizeof = config.ptrSizeof || 4,
ptrIR = config.ptrIR || 'i32' ptrIR = config.ptrIR || 'i32'
; ;
@@ -121,6 +123,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
at SIG s[0]. Throws for an unknown SIG. */ at SIG s[0]. Throws for an unknown SIG. */
const sigIR = function(s){ const sigIR = function(s){
switch(sigLetter(s)){ switch(sigLetter(s)){
case 'c': case 'C': return 'i8';
case 'i': return 'i32'; case 'i': return 'i32';
case 'p': case 'P': case 's': return ptrIR; case 'p': case 'P': case 's': return ptrIR;
case 'j': return 'i64'; case 'j': return 'i64';
@@ -129,33 +132,9 @@ self.Jaccwabyt = function StructBinderFactory(config){
} }
toss("Unhandled signature IR:",s); toss("Unhandled signature IR:",s);
}; };
/** Returns the sizeof value for the given SIG. Throws for an
unknown SIG. */
const sigSizeof = function(s){
switch(sigLetter(s)){
case 'i': return 4;
case 'p': case 'P': case 's': return ptrSizeof;
case 'j': return 8;
case 'f': return 4 /* C-side floats, not JS-side */;
case 'd': return 8;
}
toss("Unhandled signature sizeof:",s);
};
const affirmBigIntArray = BigInt64Array const affirmBigIntArray = BigInt64Array
? ()=>true : ()=>toss('BigInt64Array is not available.'); ? ()=>true : ()=>toss('BigInt64Array is not available.');
/** Returns the (signed) TypedArray associated with the type
described by the given SIG. Throws for an unknown SIG. */
/**********
const sigTypedArray = function(s){
switch(sigIR(s)) {
case 'i32': return Int32Array;
case 'i64': return affirmBigIntArray() && BigInt64Array;
case 'float': return Float32Array;
case 'double': return Float64Array;
}
toss("Unhandled signature TypedArray:",s);
};
**************/
/** Returns the name of a DataView getter method corresponding /** Returns the name of a DataView getter method corresponding
to the given SIG. */ to the given SIG. */
const sigDVGetter = function(s){ const sigDVGetter = function(s){
@@ -168,6 +147,8 @@ self.Jaccwabyt = function StructBinderFactory(config){
break; break;
} }
case 'i': return 'getInt32'; case 'i': return 'getInt32';
case 'c': return 'getInt8';
case 'C': return 'getUint8';
case 'j': return affirmBigIntArray() && 'getBigInt64'; case 'j': return affirmBigIntArray() && 'getBigInt64';
case 'f': return 'getFloat32'; case 'f': return 'getFloat32';
case 'd': return 'getFloat64'; case 'd': return 'getFloat64';
@@ -186,6 +167,8 @@ self.Jaccwabyt = function StructBinderFactory(config){
break; break;
} }
case 'i': return 'setInt32'; case 'i': return 'setInt32';
case 'c': return 'setInt8';
case 'C': return 'setUint8';
case 'j': return affirmBigIntArray() && 'setBigInt64'; case 'j': return affirmBigIntArray() && 'setBigInt64';
case 'f': return 'setFloat32'; case 'f': return 'setFloat32';
case 'd': return 'setFloat64'; case 'd': return 'setFloat64';
@@ -199,7 +182,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
*/ */
const sigDVSetWrapper = function(s){ const sigDVSetWrapper = function(s){
switch(sigLetter(s)) { switch(sigLetter(s)) {
case 'i': case 'f': case 'd': return Number; case 'i': case 'f': case 'c': case 'C': case 'd': return Number;
case 'j': return affirmBigIntArray() && BigInt; case 'j': return affirmBigIntArray() && BigInt;
case 'p': case 'P': case 's': case 'p': case 'P': case 's':
switch(ptrSizeof){ switch(ptrSizeof){
@@ -211,36 +194,14 @@ self.Jaccwabyt = function StructBinderFactory(config){
toss("Unhandled DataView set wrapper for signature:",s); toss("Unhandled DataView set wrapper for signature:",s);
}; };
/** Returns the given struct and member name in a form suitable for
debugging and error output. */
const sPropName = (s,k)=>s+'::'+k; const sPropName = (s,k)=>s+'::'+k;
const __propThrowOnSet = function(structName,propName){ const __propThrowOnSet = function(structName,propName){
return ()=>toss(sPropName(structName,propName),"is read-only."); return ()=>toss(sPropName(structName,propName),"is read-only.");
}; };
/**
When C code passes a pointer of a bound struct to back into
a JS function via a function pointer struct member, it
arrives in JS as a number (pointer).
StructType.instanceForPointer(ptr) can be used to get the
instance associated with that pointer, and __ptrBacklinks
holds that mapping. WeakMap keys must be objects, so we
cannot use a weak map to map pointers to instances. We use
the StructType constructor as the WeakMap key, mapped to a
plain, prototype-less Object which maps the pointers to
struct instances. That arrangement gives us a
per-StructType type-safe way to resolve pointers.
*/
const __ptrBacklinks = new WeakMap();
/**
Similar to __ptrBacklinks but is scoped at the StructBinder
level and holds pointer-to-object mappings for all struct
instances created by any struct from any StructFactory
which this specific StructBinder has created. The intention
of this is to help implement more transparent handling of
pointer-type property resolution.
*/
const __ptrBacklinksGlobal = Object.create(null);
/** /**
In order to completely hide StructBinder-bound struct In order to completely hide StructBinder-bound struct
pointers from JS code, we store them in a scope-local pointers from JS code, we store them in a scope-local
@@ -261,17 +222,13 @@ self.Jaccwabyt = function StructBinderFactory(config){
const __freeStruct = function(ctor, obj, m){ const __freeStruct = function(ctor, obj, m){
if(!m) m = __instancePointerMap.get(obj); if(!m) m = __instancePointerMap.get(obj);
if(m) { if(m) {
if(obj.ondispose instanceof Function){ __instancePointerMap.delete(obj);
try{obj.ondispose()} if(Array.isArray(obj.ondispose)){
catch(e){ let x;
/*do not rethrow: destructors must not throw*/ while((x = obj.ondispose.shift())){
console.warn("ondispose() for",ctor.structName,'@',
m,'threw. NOT propagating it.',e);
}
}else if(Array.isArray(obj.ondispose)){
obj.ondispose.forEach(function(x){
try{ try{
if(x instanceof Function) x.call(obj); if(x instanceof Function) x.call(obj);
else if(x instanceof StructType) x.dispose();
else if('number' === typeof x) dealloc(x); else if('number' === typeof x) dealloc(x);
// else ignore. Strings are permitted to annotate entries // else ignore. Strings are permitted to annotate entries
// to assist in debugging. // to assist in debugging.
@@ -279,12 +236,16 @@ self.Jaccwabyt = function StructBinderFactory(config){
console.warn("ondispose() for",ctor.structName,'@', console.warn("ondispose() for",ctor.structName,'@',
m,'threw. NOT propagating it.',e); m,'threw. NOT propagating it.',e);
} }
}); }
}else if(obj.ondispose instanceof Function){
try{obj.ondispose()}
catch(e){
/*do not rethrow: destructors must not throw*/
console.warn("ondispose() for",ctor.structName,'@',
m,'threw. NOT propagating it.',e);
}
} }
delete obj.ondispose; delete obj.ondispose;
delete __ptrBacklinks.get(ctor)[m];
delete __ptrBacklinksGlobal[m];
__instancePointerMap.delete(obj);
if(ctor.debugFlags.__flags.dealloc){ if(ctor.debugFlags.__flags.dealloc){
log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""), log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""),
ctor.structName,"instance:", ctor.structName,"instance:",
@@ -300,7 +261,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
iterable: false, value: v}}; iterable: false, value: v}};
/** Allocates obj's memory buffer based on the size defined in /** Allocates obj's memory buffer based on the size defined in
DEF.sizeof. */ ctor.structInfo.sizeof. */
const __allocStruct = function(ctor, obj, m){ const __allocStruct = function(ctor, obj, m){
let fill = !m; let fill = !m;
if(m) Object.defineProperty(obj, xPtrPropName, rop(m)); if(m) Object.defineProperty(obj, xPtrPropName, rop(m));
@@ -316,8 +277,6 @@ self.Jaccwabyt = function StructBinderFactory(config){
} }
if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof); if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof);
__instancePointerMap.set(obj, m); __instancePointerMap.set(obj, m);
__ptrBacklinks.get(ctor)[m] = obj;
__ptrBacklinksGlobal[m] = obj;
}catch(e){ }catch(e){
__freeStruct(ctor, obj, m); __freeStruct(ctor, obj, m);
throw e; throw e;
@@ -339,7 +298,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
if tossIfNotFound is true, else returns undefined if not if tossIfNotFound is true, else returns undefined if not
found. The given name may be either the name of the found. The given name may be either the name of the
structInfo.members key (faster) or the key as modified by the structInfo.members key (faster) or the key as modified by the
memberPrefix/memberSuffix settings. memberPrefix and memberSuffix settings.
*/ */
const __lookupMember = function(structInfo, memberName, tossIfNotFound=true){ const __lookupMember = function(structInfo, memberName, tossIfNotFound=true){
let m = structInfo.members[memberName]; let m = structInfo.members[memberName];
@@ -361,21 +320,11 @@ self.Jaccwabyt = function StructBinderFactory(config){
framework's native format or in Emscripten format. framework's native format or in Emscripten format.
*/ */
const __memberSignature = function f(obj,memberName,emscriptenFormat=false){ const __memberSignature = function f(obj,memberName,emscriptenFormat=false){
if(!f._) f._ = (x)=>x.replace(/[^vipPsjrd]/g,"").replace(/[pPs]/g,'i'); if(!f._) f._ = (x)=>x.replace(/[^vipPsjrdcC]/g,"").replace(/[pPscC]/g,'i');
const m = __lookupMember(obj.structInfo, memberName, true); const m = __lookupMember(obj.structInfo, memberName, true);
return emscriptenFormat ? f._(m.signature) : m.signature; return emscriptenFormat ? f._(m.signature) : m.signature;
}; };
/**
Returns the instanceForPointer() impl for the given
StructType constructor.
*/
const __instanceBacklinkFactory = function(ctor){
const b = Object.create(null);
__ptrBacklinks.set(ctor, b);
return (ptr)=>b[ptr];
};
const __ptrPropDescriptor = { const __ptrPropDescriptor = {
configurable: false, enumerable: false, configurable: false, enumerable: false,
get: function(){return __instancePointerMap.get(this)}, get: function(){return __instancePointerMap.get(this)},
@@ -388,7 +337,9 @@ self.Jaccwabyt = function StructBinderFactory(config){
/** Impl of X.memberKeys() for StructType and struct ctors. */ /** Impl of X.memberKeys() for StructType and struct ctors. */
const __structMemberKeys = rop(function(){ const __structMemberKeys = rop(function(){
const a = []; const a = [];
Object.keys(this.structInfo.members).forEach((k)=>a.push(this.memberKey(k))); for(const k of Object.keys(this.structInfo.members)){
a.push(this.memberKey(k));
}
return a; return a;
}); });
@@ -454,15 +405,15 @@ self.Jaccwabyt = function StructBinderFactory(config){
Adds value v to obj.ondispose, creating ondispose, Adds value v to obj.ondispose, creating ondispose,
or converting it to an array, if needed. or converting it to an array, if needed.
*/ */
const __addOnDispose = function(obj, v){ const __addOnDispose = function(obj, ...v){
if(obj.ondispose){ if(obj.ondispose){
if(obj.ondispose instanceof Function){ if(!Array.isArray(obj.ondispose)){
obj.ondispose = [obj.ondispose]; obj.ondispose = [obj.ondispose];
}/*else assume it's an array*/ }
}else{ }else{
obj.ondispose = []; obj.ondispose = [];
} }
obj.ondispose.push(v); obj.ondispose.push(...v);
}; };
/** /**
@@ -477,8 +428,9 @@ self.Jaccwabyt = function StructBinderFactory(config){
const mem = alloc(u.length+1); const mem = alloc(u.length+1);
if(!mem) toss("Allocation error while duplicating string:",str); if(!mem) toss("Allocation error while duplicating string:",str);
const h = heap(); const h = heap();
let i = 0; //let i = 0;
for( ; i < u.length; ++i ) h[mem + i] = u[i]; //for( ; i < u.length; ++i ) h[mem + i] = u[i];
h.set(u, mem);
h[mem + u.length] = 0; h[mem + u.length] = 0;
//log("allocCString @",mem," =",u); //log("allocCString @",mem," =",u);
return mem; return mem;
@@ -490,6 +442,10 @@ self.Jaccwabyt = function StructBinderFactory(config){
to free any prior memory, if appropriate. The newly-allocated to free any prior memory, if appropriate. The newly-allocated
string is added to obj.ondispose so will be freed when the object string is added to obj.ondispose so will be freed when the object
is disposed. is disposed.
The given name may be either the name of the structInfo.members
key (faster) or the key as modified by the memberPrefix and
memberSuffix settings.
*/ */
const __setMemberCString = function(obj, memberName, str){ const __setMemberCString = function(obj, memberName, str){
const m = __lookupMember(obj.structInfo, memberName, true); const m = __lookupMember(obj.structInfo, memberName, true);
@@ -544,13 +500,19 @@ self.Jaccwabyt = function StructBinderFactory(config){
return __setMemberCString(this, memberName, str); return __setMemberCString(this, memberName, str);
}) })
}); });
// Function-type non-Property inherited members
Object.assign(StructType.prototype,{
addOnDispose: function(...v){
__addOnDispose(this,...v);
return this;
}
});
/** /**
"Static" properties for StructType. "Static" properties for StructType.
*/ */
Object.defineProperties(StructType, { Object.defineProperties(StructType, {
allocCString: rop(__allocCString), allocCString: rop(__allocCString),
instanceForPointer: rop((ptr)=>__ptrBacklinksGlobal[ptr]),
isA: rop((v)=>v instanceof StructType), isA: rop((v)=>v instanceof StructType),
hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]), hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]),
memberKey: __memberKeyProp memberKey: __memberKeyProp
@@ -570,7 +532,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
/*cache all available getters/setters/set-wrappers for /*cache all available getters/setters/set-wrappers for
direct reuse in each accessor function. */ direct reuse in each accessor function. */
f._ = {getters: {}, setters: {}, sw:{}}; f._ = {getters: {}, setters: {}, sw:{}};
const a = ['i','p','P','s','f','d','v()']; const a = ['i','c','C','p','P','s','f','d','v()'];
if(bigIntEnabled) a.push('j'); if(bigIntEnabled) a.push('j');
a.forEach(function(v){ a.forEach(function(v){
//const ir = sigIR(v); //const ir = sigIR(v);
@@ -579,8 +541,8 @@ self.Jaccwabyt = function StructBinderFactory(config){
f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values
for conversion */; for conversion */;
}); });
const rxSig1 = /^[ipPsjfd]$/, const rxSig1 = /^[ipPsjfdcC]$/,
rxSig2 = /^[vipPsjfd]\([ipPsjfd]*\)$/; rxSig2 = /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/;
f.sigCheck = function(obj, name, key,sig){ f.sigCheck = function(obj, name, key,sig){
if(Object.prototype.hasOwnProperty.call(obj, key)){ if(Object.prototype.hasOwnProperty.call(obj, key)){
toss(obj.structName,'already has a property named',key+'.'); toss(obj.structName,'already has a property named',key+'.');
@@ -594,7 +556,6 @@ self.Jaccwabyt = function StructBinderFactory(config){
f.sigCheck(ctor.prototype, name, key, descr.signature); f.sigCheck(ctor.prototype, name, key, descr.signature);
descr.key = key; descr.key = key;
descr.name = name; descr.name = name;
const sizeOf = sigSizeof(descr.signature);
const sigGlyph = sigLetter(descr.signature); const sigGlyph = sigLetter(descr.signature);
const xPropName = sPropName(ctor.prototype.structName,key); const xPropName = sPropName(ctor.prototype.structName,key);
const dbg = ctor.prototype.debugFlags.__flags; const dbg = ctor.prototype.debugFlags.__flags;
@@ -610,16 +571,12 @@ self.Jaccwabyt = function StructBinderFactory(config){
prop.get = function(){ prop.get = function(){
if(dbg.getter){ if(dbg.getter){
log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph), log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph),
xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf); xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof);
} }
let rc = ( let rc = (
new DataView(heap().buffer, this.pointer + descr.offset, sizeOf) new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof)
)[f._.getters[sigGlyph]](0, isLittleEndian); )[f._.getters[sigGlyph]](0, isLittleEndian);
if(dbg.getter) log("debug.getter:",xPropName,"result =",rc); if(dbg.getter) log("debug.getter:",xPropName,"result =",rc);
if(rc && isAutoPtrSig(descr.signature)){
rc = StructType.instanceForPointer(rc) || rc;
if(dbg.getter) log("debug.getter:",xPropName,"resolved =",rc);
}
return rc; return rc;
}; };
if(descr.readOnly){ if(descr.readOnly){
@@ -628,7 +585,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
prop.set = function(v){ prop.set = function(v){
if(dbg.setter){ if(dbg.setter){
log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph), log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph),
xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf, v); xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof, v);
} }
if(!this.pointer){ if(!this.pointer){
toss("Cannot set struct property on disposed instance."); toss("Cannot set struct property on disposed instance.");
@@ -644,7 +601,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
toss("Invalid value for pointer-type",xPropName+'.'); toss("Invalid value for pointer-type",xPropName+'.');
} }
( (
new DataView(heap().buffer, this.pointer + descr.offset, sizeOf) new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof)
)[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian); )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian);
}; };
} }
@@ -665,13 +622,25 @@ self.Jaccwabyt = function StructBinderFactory(config){
if(!structName) toss("Struct name is required."); if(!structName) toss("Struct name is required.");
let lastMember = false; let lastMember = false;
Object.keys(structInfo.members).forEach((k)=>{ Object.keys(structInfo.members).forEach((k)=>{
// Sanity checks of sizeof/offset info...
const m = structInfo.members[k]; const m = structInfo.members[k];
if(!m.sizeof) toss(structName,"member",k,"is missing sizeof."); if(!m.sizeof) toss(structName,"member",k,"is missing sizeof.");
else if(0!==(m.sizeof%4)){ else if(m.sizeof===1){
toss(structName,"member",k,"sizeof is not aligned."); (m.signature === 'c' || m.signature === 'C') ||
} toss("Unexpected sizeof==1 member",
else if(0!==(m.offset%4)){ sPropName(structInfo.name,k),
toss(structName,"member",k,"offset is not aligned."); "with signature",m.signature);
}else{
// sizes and offsets of size-1 members may be odd values, but
// others may not.
if(0!==(m.sizeof%4)){
console.warn("Invalid struct member description =",m,"from",structInfo);
toss(structName,"member",k,"sizeof is not aligned. sizeof="+m.sizeof);
}
if(0!==(m.offset%4)){
console.warn("Invalid struct member description =",m,"from",structInfo);
toss(structName,"member",k,"offset is not aligned. offset="+m.offset);
}
} }
if(!lastMember || lastMember.offset < m.offset) lastMember = m; if(!lastMember || lastMember.offset < m.offset) lastMember = m;
}); });
@@ -697,27 +666,9 @@ self.Jaccwabyt = function StructBinderFactory(config){
}; };
Object.defineProperties(StructCtor,{ Object.defineProperties(StructCtor,{
debugFlags: debugFlags, debugFlags: debugFlags,
disposeAll: rop(function(){
const map = __ptrBacklinks.get(StructCtor);
Object.keys(map).forEach(function(ptr){
const b = map[ptr];
if(b) __freeStruct(StructCtor, b, ptr);
});
__ptrBacklinks.set(StructCtor, Object.create(null));
return StructCtor;
}),
instanceForPointer: rop(__instanceBacklinkFactory(StructCtor)),
isA: rop((v)=>v instanceof StructCtor), isA: rop((v)=>v instanceof StructCtor),
memberKey: __memberKeyProp, memberKey: __memberKeyProp,
memberKeys: __structMemberKeys, memberKeys: __structMemberKeys,
resolveToInstance: rop(function(v, throwIfNot=false){
if(!(v instanceof StructCtor)){
v = Number.isSafeInteger(v)
? StructCtor.instanceForPointer(v) : undefined;
}
if(!v && throwIfNot) toss("Value is-not-a",StructCtor.structName);
return v;
}),
methodInfoForKey: rop(function(mKey){ methodInfoForKey: rop(function(mKey){
}), }),
structInfo: rop(structInfo), structInfo: rop(structInfo),
@@ -735,7 +686,6 @@ self.Jaccwabyt = function StructBinderFactory(config){
); );
return StructCtor; return StructCtor;
}; };
StructBinder.instanceForPointer = StructType.instanceForPointer;
StructBinder.StructType = StructType; StructBinder.StructType = StructType;
StructBinder.config = config; StructBinder.config = config;
StructBinder.allocCString = __allocCString; StructBinder.allocCString = __allocCString;

View File

@@ -4,7 +4,6 @@ Jaccwabyt 🐇
**Jaccwabyt**: _JavaScript ⇄ C Struct Communication via WASM Byte **Jaccwabyt**: _JavaScript ⇄ C Struct Communication via WASM Byte
Arrays_ Arrays_
Welcome to Jaccwabyt, a JavaScript API which creates bindings for Welcome to Jaccwabyt, a JavaScript API which creates bindings for
WASM-compiled C structs, defining them in such a way that changes to WASM-compiled C structs, defining them in such a way that changes to
their state in JS are visible in C/WASM, and vice versa, permitting their state in JS are visible in C/WASM, and vice versa, permitting
@@ -27,8 +26,28 @@ are based solely on feature compatibility tables provided at
**Formalities:** **Formalities:**
- Author: [Stephan Beal][sgb] - Author: [Stephan Beal][sgb]
- License: Public Domain - Project Homes:
- Project Home: <https://fossil.wanderinghorse.net/r/jaccwabyt> - <https://fossil.wanderinghorse.net/r/jaccwabyt>\
Is the primary home but...
- <https://sqlite.org/src/dir/ext/wasm/jaccwabyt>\
... most development happens here.
The license for both this documentation and the software it documents
is the same as [sqlite3][], the project from which this spinoff
project was spawned:
-----
> 2022-06-30:
>
> 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 name='overview'></a> <a name='overview'></a>
Table of Contents Table of Contents
@@ -205,7 +224,6 @@ simply look like:
The StructBinder factory function returns a function which can then be The StructBinder factory function returns a function which can then be
used to create bindings for our structs. used to create bindings for our structs.
<a name='step-2'></a> <a name='step-2'></a>
Step 2: Create a Struct Description Step 2: Create a Struct Description
------------------------------------------------------------ ------------------------------------------------------------
@@ -281,21 +299,29 @@ supported letters are:
signature entry. signature entry.
- **`f`** = `float` (4 bytes) - **`f`** = `float` (4 bytes)
- **`d`** = `double` (8 bytes) - **`d`** = `double` (8 bytes)
- **`p`** = `int32` (but see below!) - **`c`** = `int8` (1 byte) char - see notes below!
- **`C`** = `uint8` (1 byte) unsigned char - see notes below!
- **`p`** = `int32` (see notes below!)
- **`P`** = Like `p` but with extra handling. Described below. - **`P`** = Like `p` but with extra handling. Described below.
- **`s`** = like `int32` but is a _hint_ that it's a pointer to a string - **`s`** = like `int32` but is a _hint_ that it's a pointer to a
so that _some_ (very limited) contexts may treat it as such, noting string so that _some_ (very limited) contexts may treat it as such,
such algorithms must, for lack of information to the contrary, noting that such algorithms must, for lack of information to the
assume both that the encoding is UTF-8 and that the pointer's member contrary, assume both that the encoding is UTF-8 and that the
is NUL-terminated. If that is _not_ the case for a given string pointer's member is NUL-terminated. If that is _not_ the case for a
member, do not use `s`: use `i` or `p` instead and do any string given string member, do not use `s`: use `i` or `p` instead and do
handling yourself. any string handling yourself.
Noting that: Noting that:
- All of these types are numeric. Attempting to set any struct-bound - **All of these types are numeric**. Attempting to set any
property to a non-numeric value will trigger an exception except in struct-bound property to a non-numeric value will trigger an
cases explicitly noted otherwise. exception except in cases explicitly noted otherwise.
- **"Char" types**: WASM does not define an `int8` type, nor does it
distinguish between signed and unsigned. This API treats `c` as
`int8` and `C` as `uint8` for purposes of getting and setting values
when using the `DataView` class. It is _not_ recommended that client
code use these types in new WASM-capable code, but they were added
for the sake of binding some immutable legacy code to WASM.
> Sidebar: Emscripten's public docs do not mention `p`, but their > Sidebar: Emscripten's public docs do not mention `p`, but their
generated code includes `p` as an alias for `i`, presumably to mean generated code includes `p` as an alias for `i`, presumably to mean
@@ -317,12 +343,12 @@ Signatures in the form `x(...)` denote function-pointer members and
form `x()`. For function-type signatures, the strings are formulated form `x()`. For function-type signatures, the strings are formulated
such that they can be passed to Emscripten's `addFunction()` after such that they can be passed to Emscripten's `addFunction()` after
stripping out the `(` and `)` characters. For good measure, to match stripping out the `(` and `)` characters. For good measure, to match
the public Emscripten docs, `p` should also be replaced with `i`. In the public Emscripten docs, `p`, `c`, and `C`, should also be replaced
JavaScript that might look like: with `i`. In JavaScript that might look like:
> >
``` ```
signature.replace(/[^vipPsjfd]/g,'').replace(/[pPs]/g,'i'); signature.replace(/[^vipPsjfdcC]/g,'').replace(/[pPscC]/g,'i');
``` ```
<a name='step-2-pvsp'></a> <a name='step-2-pvsp'></a>
@@ -337,12 +363,6 @@ special use of unsigned numbers). A capital `P` changes the semantics
of plain member pointers (but not, as of this writing, function of plain member pointers (but not, as of this writing, function
pointer members) as follows: pointer members) as follows:
- When a `P`-type member is **fetched** via `myStruct.x` and its value is
a non-0 integer, [`StructBinder.instanceForPointer()`][StructBinder]
is used to try to map that pointer to a struct instance. If a match
is found, the "get" operation returns that instance instead of the
integer. If no match is found, it behaves exactly as for `p`, returning
the integer value.
- When a `P`-type member is **set** via `myStruct.x=y`, if - When a `P`-type member is **set** via `myStruct.x=y`, if
[`(y instanceof StructType)`][StructType] then the value of `y.pointer` is [`(y instanceof StructType)`][StructType] then the value of `y.pointer` is
stored in `myStruct.x`. If `y` is neither a number nor stored in `myStruct.x`. If `y` is neither a number nor
@@ -388,14 +408,11 @@ It is important to understand that creating a new instance allocates
memory on the WASM heap. We must not simply rely on garbage collection memory on the WASM heap. We must not simply rely on garbage collection
to clean up the instances because doing so will not free up the WASM to clean up the instances because doing so will not free up the WASM
heap memory. The correct way to free up that memory is to use the heap memory. The correct way to free up that memory is to use the
object's `dispose()` method. Alternately, there is a "nuclear option": object's `dispose()` method.
`MyBinder.disposeAll()` will free the memory allocated for _all_
instances which have not been manually disposed.
The following usage pattern offers one way to easily ensure proper The following usage pattern offers one way to easily ensure proper
cleanup of struct instances: cleanup of struct instances:
> >
```javascript ```javascript
const my = new MyStruct(); const my = new MyStruct();
@@ -409,11 +426,6 @@ try {
from the byte array. */ from the byte array. */
// Pass the struct to C code which takes a MyStruct pointer: // Pass the struct to C code which takes a MyStruct pointer:
aCFunction( my.pointer ); aCFunction( my.pointer );
// Type-safely check if a pointer returned from C is a MyStruct:
const x = MyStruct.instanceForPointer( anotherCFunction() );
// If it is a MyStruct, x now refers to that object. Note, however,
// that this only works for instances created in JS, as the
// pointer mapping only exists in JS space.
} finally { } finally {
my.dispose(); my.dispose();
} }
@@ -426,6 +438,16 @@ to use `try`/`finally` without a `catch`, and doing so is an ideal
match for the memory management requirements of Jaccwaby-bound struct match for the memory management requirements of Jaccwaby-bound struct
instances. instances.
It is often useful to wrap an existing instance of a C-side struct
without taking over ownership of its memory. That can be achieved by
simply passing a pointer to the constructor. For example:
```js
const m = new MyStruct( functionReturningASharedPtr() );
// calling m.dispose() will _not_ free the wrapped C-side instance
// but will trigger any ondispose handler.
```
Now that we have struct instances, there are a number of things we Now that we have struct instances, there are a number of things we
can do with them, as covered in the rest of this document. can do with them, as covered in the rest of this document.
@@ -549,20 +571,6 @@ The Struct Binder has the following members:
any of its "significant" configuration values may have undefined any of its "significant" configuration values may have undefined
results. results.
- `instanceForPointer(pointer)`
Given a pointer value relative to `config.memory`, if that pointer
resolves to a struct of _any type_ generated via the same Struct
Binder, this returns the struct instance associated with it, or
`undefined` if no struct object is mapped to that pointer. This
differs from the struct-type-specific member of the same name in
that this one is not "type-safe": it does not know the type of the
returned object (if any) and may return a struct of any
[StructType][] for which this Struct Binder has created a
constructor. It cannot return instances created via a different
[StructBinderFactory][] because each factory can hypothetically have
a different memory heap.
<a name='api-structtype'></a> <a name='api-structtype'></a>
API: Struct Type API: Struct Type
------------------------------------------------------------ ------------------------------------------------------------
@@ -582,6 +590,14 @@ only called by the [StructBinder][]-generated
has the following "static" properties (^Which are accessible from has the following "static" properties (^Which are accessible from
individual instances via `theInstance.constructor`.): individual instances via `theInstance.constructor`.):
- `addOnDispose(...value)`\
If this object has no `ondispose` property, this function creates it
as an array and pushes the given value(s) onto it. If the object has
a function-typed `ondispose` property, this call replaces it with an
array and moves that function into the array. In all other cases,
`ondispose` is assumed to be an array and the argument(s) is/are
appended to it. Returns `this`.
- `allocCString(str)` - `allocCString(str)`
Identical to the [StructBinder][] method of the same name. Identical to the [StructBinder][] method of the same name.
@@ -591,9 +607,6 @@ individual instances via `theInstance.constructor`.):
[struct's constructor][StructCtors]. If true, the memory is owned by [struct's constructor][StructCtors]. If true, the memory is owned by
someone other than the object and must outlive the object. someone other than the object and must outlive the object.
- `instanceForPointer(pointer)`
Works identically to the [StructBinder][] method of the same name.
- `isA(value)` - `isA(value)`
Returns true if its argument is a StructType instance _from the same Returns true if its argument is a StructType instance _from the same
[StructBinder][]_ as this StructType. [StructBinder][]_ as this StructType.
@@ -619,14 +632,15 @@ legally be called on concrete struct instances unless noted otherwise:
- If it is a function, it is called with the struct object as its `this`. - If it is a function, it is called with the struct object as its `this`.
That method must not throw - if it does, the exception will be That method must not throw - if it does, the exception will be
ignored. ignored.
- If it is an array, it may contain functions, pointers, and/or JS - If it is an array, it may contain functions, pointers, other
strings. If an entry is a function, it is called as described [StructType] instances, and/or JS strings. If an entry is a
above. If it's a number, it's assumed to be a pointer and is function, it is called as described above. If it's a number, it's
passed to the `dealloc()` function configured for the parent assumed to be a pointer and is passed to the `dealloc()` function
[StructBinder][]. If it's a JS string, it's assumed to be a configured for the parent [StructBinder][]. If it's a
helpful description of the next entry in the list and is simply [StructType][] instance then its `dispose()` method is called. If
ignored. Strings are supported primarily for use as debugging it's a JS string, it's assumed to be a helpful description of the
information. next entry in the list and is simply ignored. Strings are
supported primarily for use as debugging information.
- Some struct APIs will manipulate the `ondispose` member, creating - Some struct APIs will manipulate the `ondispose` member, creating
it as an array or converting it from a function to array as it as an array or converting it from a function to array as
needed. needed.
@@ -738,21 +752,6 @@ pointer can be taken over using something like
These constructors have the following "static" members: These constructors have the following "static" members:
- `disposeAll()`
For each instance of this struct, the equivalent of its `dispose()`
method is called. This frees all WASM-allocated memory associated
with _all_ instances and clears the `instanceForPointer()`
mappings. Returns `this`.
- `instanceForPointer(pointer)`
Given a pointer value (accessible via the `pointer` property of all
struct instances) which ostensibly refers to an instance of this
class, this returns the instance associated with it, or `undefined`
if no object _of this specific struct type_ is mapped to that
pointer. When C-side code calls back into JS code and passes a
pointer to an object, this function can be used to type-safely
"cast" that pointer back to its original object.
- `isA(value)` - `isA(value)`
Returns true if its argument was created by this constructor. Returns true if its argument was created by this constructor.
@@ -762,15 +761,6 @@ These constructors have the following "static" members:
- `memberKeys(string)` - `memberKeys(string)`
Works exactly as documented for [StructType][]. Works exactly as documented for [StructType][].
- `resolveToInstance(value [,throwIfNot=false])`
Works like `instanceForPointer()` but accepts either an instance
of this struct type or a pointer which resolves to one.
It returns an instance of this struct type on success.
By default it returns a falsy value if its argument is not,
or does not resolve to, an instance of this struct type,
but if passed a truthy second argument then it will throw
instead.
- `structInfo` - `structInfo`
The structure description passed to [StructBinder][] when this The structure description passed to [StructBinder][] when this
constructor was generated. constructor was generated.

View File

@@ -192,6 +192,177 @@
fossil-doc block so that this part can work without modification in fossil-doc block so that this part can work without modification in
the wasm docs repo. */</script> the wasm docs repo. */</script>
<script>(async function(){ <script>(async function(){
const apiLinks = Object.assign(Object.create(null),{
sqlite3_aggregate_context: 'www:/c3ref/aggregate_context.html',
sqlite3_bind_blob: 'www:/c3ref/bind_blob.html',
sqlite3_bind_double: 'www:/c3ref/bind_blob.html',
sqlite3_bind_int: 'www:/c3ref/bind_blob.html',
sqlite3_bind_int64: 'www:/c3ref/bind_blob.html',
sqlite3_bind_null: 'www:/c3ref/bind_blob.html',
sqlite3_bind_parameter_count: 'www:/c3ref/bind_parameter_count.html',
sqlite3_bind_parameter_index: 'www:/c3ref/bind_parameter_index.html',
sqlite3_bind_pointer: 'www:/c3ref/bind_blob.html',
sqlite3_bind_text: 'www:/c3ref/bind_blob.html',
sqlite3_busy_handler: 'www:/c3ref/busy_handler.html',
sqlite3_busy_timeout: 'www:/c3ref/busy_timeout.html',
sqlite3_changes: 'www:/c3ref/changes.html',
sqlite3_changes64: 'www:/c3ref/changes.html',
sqlite3_clear_bindings: 'www:/c3ref/clear_bindings.html',
sqlite3_close_v2: 'www:/c3ref/close.html',
sqlite3_collation_needed: 'www:/c3ref/collation_needed.html',
sqlite3_column_blob: 'www:/c3ref/column_blob.html',
sqlite3_column_bytes: 'www:/c3ref/column.html',
sqlite3_column_count: 'www:/c3ref/column_count.html',
sqlite3_column_double: 'www:/c3ref/column_blob.html',
sqlite3_column_int: 'www:/c3ref/column_blob.html',
sqlite3_column_int64: 'www:/c3ref/column_blob.html',
sqlite3_column_name: 'www:/c3ref/column_name.html',
sqlite3_column_text: 'www:/c3ref/column_blob.html',
sqlite3_column_type: 'www:/c3ref/column_blob.html',
sqlite3_column_value: 'www:/c3ref/column_blob.html',
sqlite3_compileoption_get: 'www:/c3ref/compileoption_get.html',
sqlite3_compileoption_used: 'www:/c3ref/compileoption_get.html',
sqlite3_complete: 'www:/c3ref/complete.html',
sqlite3_create_collation: 'www:/c3ref/create_collation.html',
sqlite3_create_collation_v2: 'www:/c3ref/create_collation.html',
sqlite3_create_function: 'wasm:/api-c-style.md#sqlite3_create_function',
sqlite3_create_function_v2: 'wasm:/api-c-style.md#sqlite3_create_function',
sqlite3_create_module: 'www:/c3ref/create_module.html',
sqlite3_create_module_v2: 'www:/c3ref/create_module.html',
sqlite3_create_window_function: 'wasm:/api-c-style.md#sqlite3_create_function',
sqlite3_db_config: 'wasm:/api-c-style.md#sqlite3_db_config',
sqlite3_data_count: 'www:/c3ref/data_count.html',
sqlite3_db_filename: 'www:/c3ref/db_filename.html',
sqlite3_db_handle: 'www:/c3ref/db_handle.html',
sqlite3_db_name: 'www:/c3ref/db_name.html',
sqlite3_db_status: 'www:/c3ref/db_status.html',
sqlite3_declare_vtab: 'www:/c3ref/declare_vtab.html',
sqlite3_deserialize: 'wasm:/api-c-style.md#sqlite3_deserialize',
sqlite3_drop_modules: 'www:/c3ref/drop_modules.html',
sqlite3_errcode: 'www:/c3ref/errcode.html',
sqlite3_errmsg: 'www:/c3ref/errcode.html',
sqlite3_error_offset: 'www:/c3ref/error_offset.html',
sqlite3_errstr: 'www:/c3ref/errcode.html',
sqlite3_exec: 'wasm:/api-c-style.md#sqlite3_exec',
sqlite3_expanded_sql: 'www:/c3ref/expanded_sql.html',
sqlite3_extended_errcode: 'www:/c3ref/errcode.html',
sqlite3_extended_result_codes: 'www:/c3ref/extended_result_codes.html',
sqlite3_file_control: 'www:/c3ref/file_control.html',
sqlite3_finalize: 'www:/c3ref/finalize.html',
sqlite3_free: 'www:/c3ref/free.html',
sqlite3_get_auxdata: 'www:/c3ref/get_auxdata.html',
sqlite3_initialize: 'www:/c3ref/initialize.html',
sqlite3_keyword_check: 'www:/c3ref/keyword_check.html',
sqlite3_keyword_count: 'www:/c3ref/keyword_check.html',
sqlite3_keyword_name: 'www:/c3ref/keyword_check.html',
sqlite3_last_insert_rowid: 'www:/c3ref/last_insert_rowid.html',
sqlite3_libversion: 'www:/c3ref/libversion.html',
sqlite3_libversion_number: 'www:/c3ref/libversion.html',
sqlite3_limit: 'www:/c3ref/limit.html',
sqlite3_malloc: 'www:/c3ref/free.html',
sqlite3_malloc64: 'www:/c3ref/free.html',
sqlite3_msize: 'www:/c3ref/free.html',
sqlite3_open: 'www:/c3ref/open.html',
sqlite3_open_v2: 'www:/c3ref/open.html',
sqlite3_overload_function: 'www:/c3ref/overload_function.html',
sqlite3_prepare_v2: 'wasm:/api-c-style.md#sqlite3_prepare_v2',
sqlite3_prepare_v3: 'wasm:/api-c-style.md#sqlite3_prepare_v2',
sqlite3_progress_handler: 'www:/c3ref/progress_handler.html',
sqlite3_randomness: 'wasm:/api-c-style.md#sqlite3_randomness',
sqlite3_realloc: 'www:/c3ref/free.html',
sqlite3_realloc64: 'www:/c3ref/free.html',
sqlite3_reset: 'www:/c3ref/reset.html',
sqlite3_result_blob: 'www:/c3ref/result_blob.html',
sqlite3_result_double: 'www:/c3ref/result_blob.html',
sqlite3_result_error: 'www:/c3ref/result_blob.html',
sqlite3_result_error_code: 'www:/c3ref/result_blob.html',
sqlite3_result_error_nomem: 'www:/c3ref/result_blob.html',
sqlite3_result_error_toobig: 'www:/c3ref/result_blob.html',
sqlite3_result_int: 'www:/c3ref/result_blob.html',
sqlite3_result_int64: 'www:/c3ref/result_blob.html',
sqlite3_result_null: 'www:/c3ref/result_blob.html',
sqlite3_result_pointer: 'www:/c3ref/result_blob.html',
sqlite3_result_subtype: 'www:/c3ref/result_subtype.html',
sqlite3_result_text: 'www:/c3ref/result_blob.html',
sqlite3_result_zeroblob: 'www:/c3ref/result_blob.html',
sqlite3_result_zeroblob64: 'www:/c3ref/result_blob.html',
sqlite3_serialize: 'www:/c3ref/serialize.html',
sqlite3_set_auxdata: 'www:/c3ref/set_auxdata.html',
sqlite3_set_last_insert_rowid: 'www:/c3ref/set_last_insert_rowid',
sqlite3_shutdown: 'www:/c3ref/initialize.html',
sqlite3_sourceid: 'www:/c3ref/libversion.html',
sqlite3_sql: 'www:/c3ref/expanded_sql.html',
sqlite3_status: 'www:/c3ref/status.html',
sqlite3_status64: 'www:/c3ref/status.html',
sqlite3_step: 'www:/c3ref/step.html',
sqlite3_stmt_isexplain: 'www:/c3ref/stmt_isexplain.html',
sqlite3_stmt_readonly: 'www:/c3ref/stmt_readonly.html',
sqlite3_stmt_status: 'www:/c3ref/stmt_status.html',
sqlite3_strglob: 'www:/c3ref/strglob.html',
sqlite3_stricmp: 'www:/c3ref/stricmp.html',
sqlite3_strlike: 'www:/c3ref/strlike.html',
sqlite3_strnicmp: 'www:/c3ref/strnicmp.html',
sqlite3_table_column_metadata: 'www:/c3ref/table_column_metadata.html',
sqlite3_total_changes: 'www:/c3ref/total_changes.html',
sqlite3_total_changes64: 'www:/c3ref/total_changes.html',
sqlite3_trace_v2: 'www:/c3ref/trace_v2.html',
sqlite3_txn_state: 'www:/c3ref/txn_state.html',
sqlite3_uri_boolean: 'www:/c3ref/uri_boolean.html',
sqlite3_uri_int64: 'www:/c3ref/uri_boolean.html',
sqlite3_uri_key: 'www:/c3ref/uri_boolean.html',
sqlite3_uri_parameter: 'www:/c3ref/uri_boolean.html',
sqlite3_user_data: 'www:/c3ref/user_data.html',
sqlite3_value_blob: 'www:/c3ref/value_blob.html',
sqlite3_value_bytes: 'www:/c3ref/value_blob.html',
sqlite3_value_double: 'www:/c3ref/value_blob.html',
sqlite3_value_dup: 'www:/c3ref/value_dup.html',
sqlite3_value_free: 'www:/c3ref/value_dup.html',
sqlite3_value_frombind: 'www:/c3ref/value_blob.html',
sqlite3_value_int: 'www:/c3ref/value_blob.html',
sqlite3_value_int64: 'www:/c3ref/value_blob.html',
sqlite3_value_nochange: 'www:/c3ref/value_blob.html',
sqlite3_value_numeric_type: 'www:/c3ref/value_blob.html',
sqlite3_value_pointer: 'www:/c3ref/value_blob.html',
sqlite3_value_subtype: 'www:/c3ref/value_subtype.html',
sqlite3_value_text: 'www:/c3ref/value_blob.html',
sqlite3_value_type: 'www:/c3ref/value_blob.html',
sqlite3_vfs_find: 'www:/c3ref/vfs_find.html',
sqlite3_vfs_register: 'www:/c3ref/vfs_find.html',
sqlite3_vfs_unregister: 'www:/c3ref/vfs_find.html',
sqlite3_vtab_collation: 'www:/c3ref/vtab_collation.html',
sqlite3_vtab_config: 'www:/c3ref/vtab_config.html',
sqlite3_vtab_distinct: 'www:/c3ref/vtab_distinct.html',
sqlite3_vtab_in: 'www:/c3ref/vtab_in.html',
sqlite3_vtab_in_first: 'www:/c3ref/vtab_in_first.html',
sqlite3_vtab_in_next: 'www:/c3ref/vtab_in_next.html',
sqlite3_vtab_nochange: 'www:/c3ref/vtab_nochange.html',
sqlite3_vtab_on_conflict: 'www:/c3ref/vtab_on_conflict.html',
sqlite3_vtab_rhs_value: 'www:/c3ref/vtab_rhs_value.html',
sqlite3_column_js: 'wasm:/api-c-style.md#sqlite3_column_js',
sqlite3_js_aggregate_context: 'wasm:/api-c-style.md#sqlite3_js_aggregate_context',
sqlite3_js_db_export: 'wasm:/api-c-style.md#sqlite3_js_db_export',
sqlite3_js_db_uses_vfs: 'wasm:/api-c-style.md#sqlite3_js_db_uses_vfs',
sqlite3_js_db_vfs: 'wasm:/api-c-style.md#sqlite3_js_db_vfs',
sqlite3_js_kvvfs_clear: 'wasm:/api-c-style.md#sqlite3_js_kvvfs',
sqlite3_js_kvvfs_size: 'wasm:/api-c-style.md#sqlite3_js_kvvfs',
sqlite3_js_rc_str: 'wasm:/api-c-style.md#sqlite3_js_rc_str',
sqlite3_js_vfs_create_file: 'wasm:/api-c-style.md#sqlite3_js_vfs_create_file',
sqlite3_js_vfs_list: 'wasm:/api-c-style.md#sqlite3_js_vfs_list',
sqlite3_result_error_js: 'wasm:/api-c-style.md#sqlite3_result_error_js',
sqlite3_result_js: 'wasm:/api-c-style.md#sqlite3_result_js',
sqlite3_value_to_js: 'wasm:/api-c-style.md#sqlite3_value_to_js',
sqlite3_values_to_js: 'wasm:/api-c-style.md#sqlite3_values_to_js',
xform: (v)=>{
if(v){
return v.replace('www:','https://sqlite.org')
.replace('wasm:','https://sqlite.org/wasm/doc/trunk');
}else{
return undefined;
}
}
});
const eNew = (tag,parent)=>{ const eNew = (tag,parent)=>{
const e = document.createElement(tag); const e = document.createElement(tag);
if(parent) parent.appendChild(e); if(parent) parent.appendChild(e);
@@ -202,6 +373,16 @@
e.innerText = label; e.innerText = label;
return e; return e;
}; };
const eLink = (label,url,parent)=>{
const w = eNew('span',parent);
const e = eNew('a',w);
if(url){
e.href = url;
e.target = 'sqlite3-api-docs';
}
e.innerText = label;
return w;
};
const E = (sel)=>document.querySelector(sel); const E = (sel)=>document.querySelector(sel);
const EAll = (sel)=>document.querySelectorAll(sel); const EAll = (sel)=>document.querySelectorAll(sel);
const eFuncs = E('#list-functions'), const eFuncs = E('#list-functions'),
@@ -211,9 +392,9 @@
}; };
const renderFunc = function(name){ const renderFunc = function(name){
let lbl = name+'()'; let lbl = name+'()';
const e = eLi(lbl, eFuncs);; const e = eLink(lbl, apiLinks.xform(apiLinks[name]), eFuncs);
if(name.startsWith('sqlite3_js') if(name.indexOf('_js')>0
|| name.startsWith('sqlite3_wasm')){ || name.indexOf('_wasm')>0){
e.classList.add('func-wasm'); e.classList.add('func-wasm');
} }
}; };
@@ -257,20 +438,28 @@
); );
/* sqlite3_...() and SQLITE_... */ /* sqlite3_...() and SQLITE_... */
const lists = {c: [], f: []}; const lists = {c: [/*constants*/], f: [/*functions*/],
for(const [k,v] of Object.entries(capi)){ s: [/*structs*/]};
if(k.startsWith('SQLITE_')) lists.c.push(k); /* Exclude these from the function list... */
else if(k.startsWith('sqlite3_')) lists.f.push(k);
}
const excludeCapi = [ const excludeCapi = [
// WASMFS stuff:
'sqlite3_wasmfs_filename_is_persistent', 'sqlite3_wasmfs_filename_is_persistent',
'sqlite3_wasmfs_opfs_dir' 'sqlite3_wasmfs_opfs_dir'
]; ];
for(const [k,v] of Object.entries(capi)){
if(k.startsWith('SQLITE_')){
lists.c.push(k);
}else if(k.startsWith('sqlite3_')){
if(excludeCapi.indexOf(k)>=0) continue;
if(v.structInfo){
// assume this is a StructType-type.
continue;
}
lists.f.push(k);
}
}
lists.c.sort().forEach(renderConst); lists.c.sort().forEach(renderConst);
lists.f lists.f.sort().forEach(renderFunc);
.filter((v)=>excludeCapi.indexOf(v)<0)
.sort()
.forEach(renderFunc);
lists.c = lists.f = null; lists.c = lists.f = null;
renderX(E('#list-oo1'), sqlite3.oo1, renderX(E('#list-oo1'), sqlite3.oo1,

View File

@@ -75,7 +75,7 @@
</li> </li>
<li>The easiest way to try different optimization levels is, <li>The easiest way to try different optimization levels is,
from this directory: from this directory:
<pre>$ rm -f speedtest1.js; make -e emcc_opt='-O2' speedtest1.js</pre> <pre>$ rm -f jswasm/speedtest1.js; make -e emcc_opt='-O2' speedtest1</pre>
Then reload this page. -O2 seems to consistently produce the fastest results. Then reload this page. -O2 seems to consistently produce the fastest results.
</li> </li>
</ul> </ul>

View File

@@ -40,9 +40,9 @@
<script src="jswasm/speedtest1.js"></script> <script src="jswasm/speedtest1.js"></script>
<script>(function(){ <script>(function(){
/** /**
If this environment contains OPFS, this function initializes it and If this environment contains WASMFS with OPFS, this function
returns the name of the dir on which OPFS is mounted, else it returns initializes it and returns the name of the dir on which OPFS is
an empty string. mounted, else it returns an empty string.
*/ */
const wasmfsDir = function f(wasmUtil){ const wasmfsDir = function f(wasmUtil){
if(undefined !== f._) return f._; if(undefined !== f._) return f._;

View File

@@ -7,11 +7,7 @@
<link rel="stylesheet" href="../common/emscripten.css"/> <link rel="stylesheet" href="../common/emscripten.css"/>
<link rel="stylesheet" href="../common/testing.css"/> <link rel="stylesheet" href="../common/testing.css"/>
<title>sqlite3 tester #1: Worker thread</title> <title>sqlite3 tester #1: Worker thread</title>
<style> <style></style>
body {
font-family: monospace;
}
</style>
</head> </head>
<body> <body>
<h1 id='color-target'>sqlite3 tester #1: Worker thread</h1> <h1 id='color-target'>sqlite3 tester #1: Worker thread</h1>

View File

@@ -13,14 +13,9 @@ ES6 Module in UI thread
UI thread UI thread
//#endif //#endif
</title> </title>
<style> <style></style>
body {
font-family: monospace;
}
</style>
</head> </head>
<body> <body><h1 id='color-target'></h1>
<h1 id='color-target'></h1>
<div>See <a href='tester1-worker.html' target='tester1-worker.html'>tester1-worker.html</a> <div>See <a href='tester1-worker.html' target='tester1-worker.html'>tester1-worker.html</a>
for the Worker-thread variant.</div> for the Worker-thread variant.</div>
<div class='input-wrapper'> <div class='input-wrapper'>

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
C Remove\sthe\sSQLITE_PREPARE_SAFEOPT\sflag.\s\sThe\sname\sis\sobsolete\sand\sit\sis\sat\sthe\nwrong\slevel.\s\sInstead\suse\sthe\sSF_UpdateFrom\sflags\son\sthe\sSelect\sobject. C Merge\srecent\strunk\schanges\sinto\sthe\scoroutine-exp2\sbranch.
D 2022-12-09T18:26:15.981 D 2022-12-13T00:51:34.741
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -357,7 +357,7 @@ F ext/rbu/rbu7.test ae25f47b56f178197fc1098537a35a39176cc73d1629b03dc9d795929fc3
F ext/rbu/rbu8.test b98a6fc58ead84a0e6ddee775b9702cd981f318d5d4fd1d4df0fa0c40db7251b F ext/rbu/rbu8.test b98a6fc58ead84a0e6ddee775b9702cd981f318d5d4fd1d4df0fa0c40db7251b
F ext/rbu/rbu9.test 0e4d985e25620d61920597e8ea69c871c9e8c1f5a0be2ae9fa70bb641d74378c F ext/rbu/rbu9.test 0e4d985e25620d61920597e8ea69c871c9e8c1f5a0be2ae9fa70bb641d74378c
F ext/rbu/rbuA.test b34a90cb495682c25b5fc03a9d5e7a4fc99541c29256f25e2e2a4f6542b4f5b3 F ext/rbu/rbuA.test b34a90cb495682c25b5fc03a9d5e7a4fc99541c29256f25e2e2a4f6542b4f5b3
F ext/rbu/rbuB.test 52b07158824c6927b7e25554ace92a695cdebfc296ae3d308ac386984aded9bc F ext/rbu/rbuB.test 8d1f141711be8122739853d876af4306bc756d925499577f9b917ec1f1c5ae65
F ext/rbu/rbuC.test 80f1cc2fb74f44b1128fd0ed8eedab3a76fefeb72a947860e2869ef76fc8dc6b F ext/rbu/rbuC.test 80f1cc2fb74f44b1128fd0ed8eedab3a76fefeb72a947860e2869ef76fc8dc6b
F ext/rbu/rbu_common.tcl 60d904133ff843fe72cc0514e9dd2486707181e6e0fbab20979da28c48d21de9 F ext/rbu/rbu_common.tcl 60d904133ff843fe72cc0514e9dd2486707181e6e0fbab20979da28c48d21de9
F ext/rbu/rbubusy.test f38ef557358564491b8a2ee70e4cad31e40fcea57a16f27bc56ba40a59bbde50 F ext/rbu/rbubusy.test f38ef557358564491b8a2ee70e4cad31e40fcea57a16f27bc56ba40a59bbde50
@@ -386,7 +386,7 @@ F ext/rbu/rbuvacuum.test 55e101e90168c2b31df6c9638fe73dc7f7cc666b6142266d1563697
F ext/rbu/rbuvacuum2.test 2643b58f4d8d3573db0f93faae18805a35ab162b4c55ff6b656062ff432ed55b F ext/rbu/rbuvacuum2.test 2643b58f4d8d3573db0f93faae18805a35ab162b4c55ff6b656062ff432ed55b
F ext/rbu/rbuvacuum3.test 8addd82e4b83b4c93fa47428eae4fd0dbf410f8512c186f38e348feb49ba03dc F ext/rbu/rbuvacuum3.test 8addd82e4b83b4c93fa47428eae4fd0dbf410f8512c186f38e348feb49ba03dc
F ext/rbu/rbuvacuum4.test a78898e438a44803eb2bc897ba3323373c9f277418e2d6d76e90f2f1dbccfd10 F ext/rbu/rbuvacuum4.test a78898e438a44803eb2bc897ba3323373c9f277418e2d6d76e90f2f1dbccfd10
F ext/rbu/sqlite3rbu.c c4ba7901b2d3e0c7845f30840e3ffb35c6f999d6da0d80f121866491f982794c F ext/rbu/sqlite3rbu.c 64d105c7c6c95272e7c12142cd021059214387a0d51e262bb0663285a2b91660
F ext/rbu/sqlite3rbu.h 02d981e2d39c151391759e1a400e29c7388730812957ac3db8dad7f6c9f9cfc8 F ext/rbu/sqlite3rbu.h 02d981e2d39c151391759e1a400e29c7388730812957ac3db8dad7f6c9f9cfc8
F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055 F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055
F ext/recover/dbdata.c 8f1f75d636431de69d7977ec50fc41bfdd0c48c510d5ee7eae0cbd4164e1429a F ext/recover/dbdata.c 8f1f75d636431de69d7977ec50fc41bfdd0c48c510d5ee7eae0cbd4164e1429a
@@ -491,37 +491,37 @@ F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb
F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c
F ext/wasm/GNUmakefile bfa47f169468ca9db031105b0e336db29a88e93c3abd217d0bbb2b8731fa5413 F ext/wasm/GNUmakefile 32ad3deb3005a72d1955e28fa375e01e976cda01f0bc4ddd3fec93006fa3fa04
F ext/wasm/README-dist.txt 2d670b426fc7c613b90a7d2f2b05b433088fe65181abead970980f0a4a75ea20 F ext/wasm/README-dist.txt 2d670b426fc7c613b90a7d2f2b05b433088fe65181abead970980f0a4a75ea20
F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9 F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9
F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 89af0612bad5c651f69e629c7e9689be6d3c8a92a9010da5dd90a87c1d86817a F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 3883604dfda98352ff7ea76b2092f406d8c1ebc576e16b8c6e470fa2b1724880
F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
F ext/wasm/api/README.md 20a256f4aaae80035d2bb1c9e3e0a125570313a8d137d427471d7be10edde87a F ext/wasm/api/README.md 17fb1e10335cc87e366dec496c5b17b061f3f75cdf216e825258de34d97a3e53
F ext/wasm/api/extern-post-js.c-pp.js 8923f76c3d2213159e12d641dc750523ead5c848185dc4996fae5cc12397f88d F ext/wasm/api/extern-post-js.c-pp.js 8923f76c3d2213159e12d641dc750523ead5c848185dc4996fae5cc12397f88d
F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41 F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41
F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1 F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1
F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62 F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62
F ext/wasm/api/pre-js.c-pp.js b88499dc303c21fc3f55f2c364a0f814f587b60a95784303881169f9e91c1d5f F ext/wasm/api/pre-js.c-pp.js b88499dc303c21fc3f55f2c364a0f814f587b60a95784303881169f9e91c1d5f
F ext/wasm/api/sqlite3-api-cleanup.js 680d5ccfff54459db136a49b2199d9f879c8405d9c99af1dda0cc5e7c29056f4 F ext/wasm/api/sqlite3-api-cleanup.js 680d5ccfff54459db136a49b2199d9f879c8405d9c99af1dda0cc5e7c29056f4
F ext/wasm/api/sqlite3-api-glue.js 6fe39964605fda3b699f69365eed565b5172d29cab2c49bc057a43f9a93f9f36 F ext/wasm/api/sqlite3-api-glue.js 1ff6deb11bd192c13cbd247e333a4739e2303aad3d1a9dc68defced2d7393375
F ext/wasm/api/sqlite3-api-oo1.js 91a7d7b9203fb0f031e6ba380a644a7f871e1798b388de399c01ed4087bac9e0 F ext/wasm/api/sqlite3-api-oo1.js 6d10849609231ccd46fa11b1d3fbbe0f45d9fe84c66a0b054601036540844300
F ext/wasm/api/sqlite3-api-prologue.js 697a5989ad52a9ba7bc60b5436589bd05885ee2201d84c38c5e9af3876af3ba4 F ext/wasm/api/sqlite3-api-prologue.js 39fbca8f25219c218d631433828ede53be8d518aa9f0da480758a3ea8abc1be8
F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3 F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js f79dd8d98ef3e0b55c10bb2bee7a3840fa967318e1f577c156aafc34664271d1 F ext/wasm/api/sqlite3-opfs-async-proxy.js 7795b84b66a7a8dedc791340709b310bb497c3c72a80bef364fa2a58e2ddae3f
F ext/wasm/api/sqlite3-vfs-helper.js 4ad4faf02e1524bf0296be8452c00b5708dce6faf649468d0377e26a0b299263 F ext/wasm/api/sqlite3-v-helper.js 6f6c3e390a72e08b0a5b16a0d567d7af3c04d172831853a29d72a6f1dd40ff24 w ext/wasm/api/sqlite3-vfs-helper.js
F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 29d6487a26b2fb6a471cde52c37ffee7c27ed6a91914b308c247e0706f454ffb F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 66daf6fb6843bea615fe193109e1542efbeca24f560ee9da63375a910bb48115
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c b0babf8435f31d21f28454fb81433aa538c68b23d0a4a251f0666fdec4e71f59 F ext/wasm/api/sqlite3-wasm.c 0d3d021c7f32d4422872cb1af8a163cd4cc63c0be314eb5cf6e56931260213c8
F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b
F ext/wasm/api/sqlite3-worker1.js 1e54ea3d540161bcfb2100368a2fc0cad871a207b8336afee1c445715851ec54 F ext/wasm/api/sqlite3-worker1.js 1e54ea3d540161bcfb2100368a2fc0cad871a207b8336afee1c445715851ec54
F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8
F ext/wasm/batch-runner.js 49609e89aaac9989d6c1ad3fae268e4878e1ad7bc5fd3e5c2f44959660780b2e F ext/wasm/batch-runner.js 0dad6a02ad796f1003d3b7048947d275c4d6277f63767b8e685c27df8fdac93e
F ext/wasm/c-pp.c 92285f7bce67ed7b7020b40fde8ed0982c442b63dc33df9dfd4b658d4a6c0779 F ext/wasm/c-pp.c 92285f7bce67ed7b7020b40fde8ed0982c442b63dc33df9dfd4b658d4a6c0779
F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b
F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15
F ext/wasm/common/testing.css 35889709547d89a6109ff83b25c11bbc91d8dd43aab8722e428655ca98880a06 F ext/wasm/common/testing.css 0ff15602a3ab2bad8aef2c3bd120c7ee3fd1c2054ad2ace7e214187ae68d926f
F ext/wasm/common/whwasmutil.js c1bc5715cd96728929cc31d788b16152ccbd6b2e111d2e88fbc9725247e67b4f F ext/wasm/common/whwasmutil.js 85bdcefc3ae4b550231da1f94b196e15458828d7652b1f61f86d20873ff915ed
F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed
F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508
F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6 F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6
@@ -531,7 +531,7 @@ F ext/wasm/demo-worker1-promiser.html 1de7c248c7c2cfd4a5783d2aa154bce62d74c6de98
F ext/wasm/demo-worker1-promiser.js b85a2bb1b918db4f09dfa24419241cb3edad7791389425c2505092e9b715017d F ext/wasm/demo-worker1-promiser.js b85a2bb1b918db4f09dfa24419241cb3edad7791389425c2505092e9b715017d
F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d
F ext/wasm/demo-worker1.js a619adffc98b75b66c633b00f747b856449a134a9a0357909287d80a182d70fa F ext/wasm/demo-worker1.js a619adffc98b75b66c633b00f747b856449a134a9a0357909287d80a182d70fa
F ext/wasm/dist.make 701694188a78c9a24bf44cdf529063f4b3a0e892adc1d20ed1619252738943f1 F ext/wasm/dist.make 5523b02e824db5ab8176e3eedc2e709fe1204d8f4d6e52e8321cdf6830114b72
F ext/wasm/fiddle.make 2812c44c9bafb5be9c8767963d1b9f374d77af7795fcaa06483c03e7059dea74 F ext/wasm/fiddle.make 2812c44c9bafb5be9c8767963d1b9f374d77af7795fcaa06483c03e7059dea74
F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
F ext/wasm/fiddle/fiddle-worker.js b4a0c8ab6c0983218543ca771c45f6075449f63a1dcf290ae5a681b2cba8800d F ext/wasm/fiddle/fiddle-worker.js b4a0c8ab6c0983218543ca771c45f6075449f63a1dcf290ae5a681b2cba8800d
@@ -539,23 +539,23 @@ F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5
F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2 F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2
F ext/wasm/index-dist.html c806b6005145b71d64240606e9c6e0bf56878ee8829c66fe7486cebf34b0e6b1 F ext/wasm/index-dist.html c806b6005145b71d64240606e9c6e0bf56878ee8829c66fe7486cebf34b0e6b1
F ext/wasm/index.html f151b7c7b5cfdc066567d556acd168e769efd4e982286dc5f849a5ee69ecd0ff F ext/wasm/index.html f151b7c7b5cfdc066567d556acd168e769efd4e982286dc5f849a5ee69ecd0ff
F ext/wasm/jaccwabyt/jaccwabyt.js 95f573de1826474c9605dda620ee622fcb1673ae74f191eb324c0853aa4dcb66 F ext/wasm/jaccwabyt/jaccwabyt.js 06f2ef1ad640c26c593def3d960336e9bb789819b920516480895c38ed5f58fa
F ext/wasm/jaccwabyt/jaccwabyt.md 9aa6951b529a8b29f578ec8f0355713c39584c92cf1708f63ba0cf917cb5b68e F ext/wasm/jaccwabyt/jaccwabyt.md 37911f00db12cbcca73aa1ed72594430365f30aafae2fa9c886961de74e5e0eb
F ext/wasm/module-symbols.html 980680c8acfa3c8ae6a5aa223512d1b8e78040ced20f8ba2c382129bc73ec028 F ext/wasm/module-symbols.html 650a5ad1bd54feb39eb627b06093612932317406825f3a727d5926016a46e502
F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06 F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06
F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18 F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18
F ext/wasm/speedtest1-wasmfs.html bc28eb29b69a73864b8d7aae428448f8b7e1de81d8bfb9bba99541322054dbd0 F ext/wasm/speedtest1-wasmfs.html bc28eb29b69a73864b8d7aae428448f8b7e1de81d8bfb9bba99541322054dbd0
F ext/wasm/speedtest1-worker.html e94cecebcbb9d187647025edd04d37af9789dfba98c2cc439b549b5ae8a8bc93 F ext/wasm/speedtest1-worker.html fe6b36a63de1012bb9fb4d2fb888b6de9c589c21b0aa3ae054459b0093e077bf
F ext/wasm/speedtest1-worker.js 13b57c4a41729678a1194014afec2bd5b94435dcfc8d1039dfa9a533ac819ee1 F ext/wasm/speedtest1-worker.js 13b57c4a41729678a1194014afec2bd5b94435dcfc8d1039dfa9a533ac819ee1
F ext/wasm/speedtest1.html e4c4e5c1c8ec1ad13c995e346e4216a1df152fd2c5cd17e0793b865b2f3c5000 F ext/wasm/speedtest1.html ff048b4a623aa192e83e143e48f1ce2a899846dd42c023fdedc8772b6e3f07da
F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x
F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0 F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0
F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5 F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5
F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f
F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac
F ext/wasm/tester1-worker.html 29b1d87f7d51f70d61645719fee657f3787fe939bb695f27034c75404e8f1e6f F ext/wasm/tester1-worker.html d43f3c131d88f10d00aff3e328fed13c858d674ea2ff1ff90225506137f85aa9
F ext/wasm/tester1.c-pp.html 74aa9b31c75f12490653f814b53c3dd39f40cd3f70d6a53a716f4e8587107399 F ext/wasm/tester1.c-pp.html d34bef3d48e5cbc1c7c06882ad240fec49bf88f5f65696cc2c72c416933aa406
F ext/wasm/tester1.c-pp.js d096a8fadfd27caa680a4311b1d529551f8fe885a63dd27457c87b6008c64632 F ext/wasm/tester1.c-pp.js ee609a41cc1aabc971a6514b5d1b155f5f15d092ee015f5d03a204880532e62d
F ext/wasm/tests/opfs/concurrency/index.html 86d8ac435074d1e7007b91105f4897f368c165e8cecb6a9aa3d81f5cf5dcbe70 F ext/wasm/tests/opfs/concurrency/index.html 86d8ac435074d1e7007b91105f4897f368c165e8cecb6a9aa3d81f5cf5dcbe70
F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d
F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@@ -608,7 +608,7 @@ F src/insert.c 1b11a2e33ee52db93c02fddac67e39d00161d61b69fac2675b82f2aa68c1b61c
F src/json.c 7749b98c62f691697c7ee536b570c744c0583cab4a89200fdd0fc2aa8cc8cbd6 F src/json.c 7749b98c62f691697c7ee536b570c744c0583cab4a89200fdd0fc2aa8cc8cbd6
F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
F src/loadext.c 25663175950c5c4404b9377840b7b4c6fe5c53b415caf43634c62f442c02a9a7 F src/loadext.c 25663175950c5c4404b9377840b7b4c6fe5c53b415caf43634c62f442c02a9a7
F src/main.c 954490392b74fb215378af3c75a9e1f4f559f19cb1567e5d77f3fbbb63909b4d F src/main.c 1f75553163a3349a3918d1aae072bc8e908bbb58c949d5fec9b90bf49ff3b73e
F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23 F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de
@@ -645,9 +645,9 @@ F src/printf.c e99ee9741e79ae3873458146f59644276657340385ade4e76a5f5d1c25793764
F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633 F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
F src/select.c d4df3eb74a1b0e1bd5a95525f2b571baeef3e3bedcc9d6401152dc0e0bdd7384 F src/select.c 4ad0aa2b21a94b118f6f6f1c9baf8e932d14227e8a9a5022b86e661049681c3a
F src/shell.c.in bcf8552c82f2c84650e39a6d638373569c2035942c0497b83eef197169e0305a F src/shell.c.in 8d9dc02dd03f8fc93f3e3cdb17d8d16e8ddb985dddad213985c08186900a3ebb
F src/sqlite.h.in 1fe1836879ecbb2e28f00f44eb6092db09a2a06bf072af351c6c2466bd515496 F src/sqlite.h.in e752f82b9d71f1d42b259b1900e4b1caf0965e844d756cd5cc91cc2cf45ed925
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h c4b9fa7a7e2bcdf850cfeb4b8a91d5ec47b7a00033bc996fd2ee96cbf2741f5f F src/sqlite3ext.h c4b9fa7a7e2bcdf850cfeb4b8a91d5ec47b7a00033bc996fd2ee96cbf2741f5f
F src/sqliteInt.h ef0c1af2d7b46ce89fe698ce5f1a26d19110780ac19ec1ef5a227f7e51e97de3 F src/sqliteInt.h ef0c1af2d7b46ce89fe698ce5f1a26d19110780ac19ec1ef5a227f7e51e97de3
@@ -863,7 +863,7 @@ F test/capi3c.test 54e2dc0c8fd7c34ad1590d1be6864397da2438c95a9f5aee2f8fbc60c112e
F test/capi3d.test 8b778794af891b0dca3d900bd345fbc8ebd2aa2aae425a9dccdd10d5233dfbde F test/capi3d.test 8b778794af891b0dca3d900bd345fbc8ebd2aa2aae425a9dccdd10d5233dfbde
F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe
F test/carray01.test d55d57bf66b1af1c7ac55fae66ff4910884a8f5d21a90a18797ce386212a2634 F test/carray01.test d55d57bf66b1af1c7ac55fae66ff4910884a8f5d21a90a18797ce386212a2634
F test/cast.test 336fa21989b5170ebcaf90c24266be22dd97b3e23d1fad5ecf6ad4efb04c4423 F test/cast.test 46a5963a216c2b14220557b8636b968d6de9d3292d79616becbf7109ca00e1ad
F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef
F test/changes.test 9dd8e597d84072122fc8a4fcdea837f4a54a461e6e536053ea984303e8ca937b F test/changes.test 9dd8e597d84072122fc8a4fcdea837f4a54a461e6e536053ea984303e8ca937b
F test/changes2.test d222c0cbf5ab0ac4d7c180594e486c1bf20b2098d33e56ce33b8e12eba6823b9 F test/changes2.test d222c0cbf5ab0ac4d7c180594e486c1bf20b2098d33e56ce33b8e12eba6823b9
@@ -1819,7 +1819,7 @@ F test/vacuum6.test b137b04bf3392d3f5c3b8fda0ce85a6775a70ca112f6559f74ff52dc9ce0
F test/vacuummem.test 4b30f5b95a9ff86e9d5c20741e50a898b2dc10b0962a3211571eb165357003fb F test/vacuummem.test 4b30f5b95a9ff86e9d5c20741e50a898b2dc10b0962a3211571eb165357003fb
F test/varint.test bbce22cda8fc4d135bcc2b589574be8410614e62 F test/varint.test bbce22cda8fc4d135bcc2b589574be8410614e62
F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661
F test/view.test d16e49e89ada6137d1447777ef2a74574526a3b024e6733bf53ae2960da8c17c F test/view.test a5662e9c7425d77b1e1eb4b07fb891fa15f20317013eddcb6f6bee009f2fccc9
F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679 F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679
F test/view3.test ad8a8290ee2b55ff6ce66c9ef1ce3f1e47926273a3814e1c425293e128a95456 F test/view3.test ad8a8290ee2b55ff6ce66c9ef1ce3f1e47926273a3814e1c425293e128a95456
F test/vt02.c 33ecddc0832d4cd24e9e9fa83d868981b1e049462f4ec9080710353f6479a534 F test/vt02.c 33ecddc0832d4cd24e9e9fa83d868981b1e049462f4ec9080710353f6479a534
@@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P d125d5afdf1b0a1c64fc64f180898099af07b8290ea9da49419df75d8b455f71 P 78723a9a7e72b42d28fc5645661da17f20cedcf864819b861800ad9340007be1 f0325359d5795237b79f90f21b42d7d942c7e918137cb0231d404365d3041e81
R bf6e5a558d8631cb7969118db6751e4e R fab8476faef04e68db98cfe4c1810ebc
U drh U drh
Z 2ad12df38bfb5c0313f6be39e7ec9c86 Z 1279761382706bd160c45650b0901daf
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@@ -1 +1 @@
78723a9a7e72b42d28fc5645661da17f20cedcf864819b861800ad9340007be1 c43f433bcab29db0f1f8afd3948f5a4149e1f277c853c66f99c31f226d82bc0f

View File

@@ -1566,6 +1566,7 @@ const char *sqlite3ErrName(int rc){
case SQLITE_NOTICE_RECOVER_WAL: zName = "SQLITE_NOTICE_RECOVER_WAL";break; case SQLITE_NOTICE_RECOVER_WAL: zName = "SQLITE_NOTICE_RECOVER_WAL";break;
case SQLITE_NOTICE_RECOVER_ROLLBACK: case SQLITE_NOTICE_RECOVER_ROLLBACK:
zName = "SQLITE_NOTICE_RECOVER_ROLLBACK"; break; zName = "SQLITE_NOTICE_RECOVER_ROLLBACK"; break;
case SQLITE_NOTICE_RBU: zName = "SQLITE_NOTICE_RBU"; break;
case SQLITE_WARNING: zName = "SQLITE_WARNING"; break; case SQLITE_WARNING: zName = "SQLITE_WARNING"; break;
case SQLITE_WARNING_AUTOINDEX: zName = "SQLITE_WARNING_AUTOINDEX"; break; case SQLITE_WARNING_AUTOINDEX: zName = "SQLITE_WARNING_AUTOINDEX"; break;
case SQLITE_DONE: zName = "SQLITE_DONE"; break; case SQLITE_DONE: zName = "SQLITE_DONE"; break;

View File

@@ -2289,6 +2289,14 @@ int sqlite3ColumnsFromExprList(
return SQLITE_OK; return SQLITE_OK;
} }
/*
** This bit, when added to the "aff" parameter of
** sqlite3SelectAddColumnTypeAndCollation() means that result set
** expressions of the form "CAST(expr AS NUMERIC)" should result in
** NONE affinity rather than NUMERIC affinity.
*/
#define SQLITE_AFF_FLAG1 0x10
/* /*
** Add type and collation information to a column list based on ** Add type and collation information to a column list based on
** a SELECT statement. ** a SELECT statement.
@@ -2299,12 +2307,17 @@ int sqlite3ColumnsFromExprList(
** **
** This routine requires that all identifiers in the SELECT ** This routine requires that all identifiers in the SELECT
** statement be resolved. ** statement be resolved.
**
** The SQLITE_AFF_FLAG1 bit added to parameter aff means that a
** result set column of the form "CAST(expr AS NUMERIC)" should use
** NONE affinity rather than NUMERIC affinity. See the
** 2022-12-10 "reopen" of ticket https://sqlite.org/src/tktview/57c47526c3.
*/ */
void sqlite3SelectAddColumnTypeAndCollation( void sqlite3SelectAddColumnTypeAndCollation(
Parse *pParse, /* Parsing contexts */ Parse *pParse, /* Parsing contexts */
Table *pTab, /* Add column type information to this table */ Table *pTab, /* Add column type information to this table */
Select *pSelect, /* SELECT used to determine types and collations */ Select *pSelect, /* SELECT used to determine types and collations */
char aff /* Default affinity for columns */ char aff /* Default affinity. Maybe with SQLITE_AFF_FLAG1 too */
){ ){
sqlite3 *db = pParse->db; sqlite3 *db = pParse->db;
NameContext sNC; NameContext sNC;
@@ -2318,6 +2331,7 @@ void sqlite3SelectAddColumnTypeAndCollation(
assert( (pSelect->selFlags & SF_Resolved)!=0 ); assert( (pSelect->selFlags & SF_Resolved)!=0 );
assert( pTab->nCol==pSelect->pEList->nExpr || db->mallocFailed ); assert( pTab->nCol==pSelect->pEList->nExpr || db->mallocFailed );
if( db->mallocFailed ) return; if( db->mallocFailed ) return;
while( pSelect->pPrior ) pSelect = pSelect->pPrior;
memset(&sNC, 0, sizeof(sNC)); memset(&sNC, 0, sizeof(sNC));
sNC.pSrcList = pSelect->pSrc; sNC.pSrcList = pSelect->pSrc;
a = pSelect->pEList->a; a = pSelect->pEList->a;
@@ -2329,6 +2343,12 @@ void sqlite3SelectAddColumnTypeAndCollation(
zType = columnType(&sNC, p, 0, 0, 0); zType = columnType(&sNC, p, 0, 0, 0);
/* pCol->szEst = ... // Column size est for SELECT tables never used */ /* pCol->szEst = ... // Column size est for SELECT tables never used */
pCol->affinity = sqlite3ExprAffinity(p); pCol->affinity = sqlite3ExprAffinity(p);
if( pCol->affinity==SQLITE_AFF_NUMERIC
&& p->op==TK_CAST
&& (aff & SQLITE_AFF_FLAG1)!=0
){
pCol->affinity = SQLITE_AFF_NONE;
}
if( zType ){ if( zType ){
m = sqlite3Strlen30(zType); m = sqlite3Strlen30(zType);
n = sqlite3Strlen30(pCol->zCnName); n = sqlite3Strlen30(pCol->zCnName);
@@ -2341,7 +2361,10 @@ void sqlite3SelectAddColumnTypeAndCollation(
pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL);
} }
} }
if( pCol->affinity<=SQLITE_AFF_NONE ) pCol->affinity = aff; if( pCol->affinity<=SQLITE_AFF_NONE ){
assert( (SQLITE_AFF_FLAG1 & SQLITE_AFF_MASK)==0 );
pCol->affinity = aff & SQLITE_AFF_MASK;
}
pColl = sqlite3ExprCollSeq(pParse, p); pColl = sqlite3ExprCollSeq(pParse, p);
if( pColl ){ if( pColl ){
assert( pTab->pIndex==0 ); assert( pTab->pIndex==0 );
@@ -6215,7 +6238,7 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){
if( pSel ){ if( pSel ){
while( pSel->pPrior ) pSel = pSel->pPrior; while( pSel->pPrior ) pSel = pSel->pPrior;
sqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSel, sqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSel,
SQLITE_AFF_NONE); SQLITE_AFF_NONE|SQLITE_AFF_FLAG1);
} }
} }
} }

View File

@@ -3125,6 +3125,7 @@ static void display_scanstats(
int iId = 0; int iId = 0;
int iPid = 0; int iPid = 0;
const char *z = 0; const char *z = 0;
const char *zName = 0;
char *zText = 0; char *zText = 0;
double rEst = 0.0; double rEst = 0.0;
@@ -3137,6 +3138,7 @@ static void display_scanstats(
sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle); sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle);
sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId); sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId);
sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid); sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid);
sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NAME,f,(void*)&zName);
zText = sqlite3_mprintf("%s", z); zText = sqlite3_mprintf("%s", z);
if( nCycle>=0 || nLoop>=0 || nRow>=0 ){ if( nCycle>=0 || nLoop>=0 || nRow>=0 ){
@@ -3153,6 +3155,11 @@ static void display_scanstats(
z = sqlite3_mprintf("%z%srows=%lld", z, z ? " " : "", nRow); z = sqlite3_mprintf("%z%srows=%lld", z, z ? " " : "", nRow);
} }
if( zName && pArg->scanstatsOn>1 ){
double rpl = (double)nRow / (double)nLoop;
z = sqlite3_mprintf("%z rpl=%.1f est=%.1f", z, rpl, rEst);
}
zText = sqlite3_mprintf( zText = sqlite3_mprintf(
"% *z (%z)", -1*(nWidth-scanStatsHeight(p, ii)*3), zText, z "% *z (%z)", -1*(nWidth-scanStatsHeight(p, ii)*3), zText, z
); );
@@ -4678,7 +4685,7 @@ static const char *(azHelp[]) = {
".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)", ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)",
#endif #endif
".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off", ".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off",
".schema ?PATTERN? Show the CREATE statements matching PATTERN", ".schema ?PATTERN? Show the CREATE statements matching PATTERN",
" Options:", " Options:",
" --indent Try to pretty-print the schema", " --indent Try to pretty-print the schema",
@@ -9707,12 +9714,16 @@ static int do_meta_command(char *zLine, ShellState *p){
if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){ if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){
if( nArg==2 ){ if( nArg==2 ){
p->scanstatsOn = (u8)booleanValue(azArg[1]); if( cli_strcmp(azArg[1], "est")==0 ){
p->scanstatsOn = 2;
}else{
p->scanstatsOn = (u8)booleanValue(azArg[1]);
}
#ifndef SQLITE_ENABLE_STMT_SCANSTATUS #ifndef SQLITE_ENABLE_STMT_SCANSTATUS
raw_printf(stderr, "Warning: .scanstats not available in this build.\n"); raw_printf(stderr, "Warning: .scanstats not available in this build.\n");
#endif #endif
}else{ }else{
raw_printf(stderr, "Usage: .scanstats on|off\n"); raw_printf(stderr, "Usage: .scanstats on|off|est\n");
rc = 1; rc = 1;
} }
}else }else

View File

@@ -563,6 +563,7 @@ int sqlite3_exec(
#define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT |(12<<8)) #define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT |(12<<8))
#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) #define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8))
#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8))
#define SQLITE_NOTICE_RBU (SQLITE_NOTICE | (3<<8))
#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) #define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8))
#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) #define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8))
#define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8)) #define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8))
@@ -2184,7 +2185,7 @@ struct sqlite3_mem_methods {
** configuration for a database connection can only be changed when that ** configuration for a database connection can only be changed when that
** connection is not currently using lookaside memory, or in other words ** connection is not currently using lookaside memory, or in other words
** when the "current value" returned by ** when the "current value" returned by
** [sqlite3_db_status](D,[SQLITE_CONFIG_LOOKASIDE],...) is zero. ** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero.
** Any attempt to change the lookaside memory configuration when lookaside ** Any attempt to change the lookaside memory configuration when lookaside
** memory is in use leaves the configuration unchanged and returns ** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY].)^</dd> ** [SQLITE_BUSY].)^</dd>
@@ -2334,8 +2335,12 @@ struct sqlite3_mem_methods {
** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); ** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0);
** </ol> ** </ol>
** Because resetting a database is destructive and irreversible, the ** Because resetting a database is destructive and irreversible, the
** process requires the use of this obscure API and multiple steps to help ** process requires the use of this obscure API and multiple steps to
** ensure that it does not happen by accident. ** help ensure that it does not happen by accident. Because this
** feature must be capable of resetting corrupt databases, and
** shutting down virtual tables may require access to that corrupt
** storage, the library must abandon any installed virtual tables
** without calling their xDestroy() methods.
** **
** [[SQLITE_DBCONFIG_DEFENSIVE]] <dt>SQLITE_DBCONFIG_DEFENSIVE</dt> ** [[SQLITE_DBCONFIG_DEFENSIVE]] <dt>SQLITE_DBCONFIG_DEFENSIVE</dt>
** <dd>The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the ** <dd>The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the

View File

@@ -483,5 +483,36 @@ do_execsql_test cast-9.0 {
SELECT v1.c0 FROM v1, t0 WHERE v1.c0=0; SELECT v1.c0 FROM v1, t0 WHERE v1.c0=0;
} {0.0} } {0.0}
# Set the 2022-12-10 "reopen" of ticket [https://sqlite.org/src/tktview/57c47526c3]
#
do_execsql_test cast-9.1 {
CREATE TABLE dual(dummy TEXT);
INSERT INTO dual VALUES('X');
SELECT CAST(4 AS NUMERIC);
} {4}
do_execsql_test cast-9.2 {
SELECT CAST(4.0 AS NUMERIC);
} {4.0}
do_execsql_test cast-9.3 {
SELECT CAST(4.5 AS NUMERIC);
} {4.5}
do_execsql_test cast-9.4 {
SELECT x, typeof(x) FROM (SELECT CAST(4 AS NUMERIC) AS x) JOIN dual;
} {4 integer}
do_execsql_test cast-9.5 {
SELECT x, typeof(x) FROM dual CROSS JOIN (SELECT CAST(4 AS NUMERIC) AS x);
} {4 integer}
do_execsql_test cast-9.10 {
SELECT x, typeof(x) FROM (SELECT CAST(4.0 AS NUMERIC) AS x) JOIN dual;
} {4.0 real}
do_execsql_test cast-9.11 {
SELECT x, typeof(x) FROM dual CROSS JOIN (SELECT CAST(4.0 AS NUMERIC) AS x);
} {4.0 real}
do_execsql_test cast-9.12 {
SELECT x, typeof(x) FROM (SELECT CAST(4.5 AS NUMERIC) AS x) JOIN dual;
} {4.5 real}
do_execsql_test cast-9.13 {
SELECT x, typeof(x) FROM dual CROSS JOIN (SELECT CAST(4.5 AS NUMERIC) AS x);
} {4.5 real}
finish_test finish_test

View File

@@ -775,5 +775,27 @@ do_catchsql_test view-29.1 {
SELECT name FROM sqlite_schema ORDER BY name; SELECT name FROM sqlite_schema ORDER BY name;
} {0 {t1 t2}} } {0 {t1 t2}}
#-------------------------------------------------------------------------
# 2022-12-11. https://sqlite.org/src/info/679ed6a2
#
reset_db
do_execsql_test view-30.0 {
CREATE TABLE t0(a INT, b TEXT);
INSERT INTO t0 VALUES(1,'one');
CREATE VIEW t1 AS SELECT a, b FROM t0 UNION ALL SELECT 2, 2;
CREATE VIEW t2(a,b) AS SELECT a, b FROM t0 UNION ALL SELECT 2, 2;
}
ifcapable schema_pragmas {
do_execsql_test view-30.1 {
PRAGMA table_info = t1;
} { 0 a INT 0 {} 0 1 b TEXT 0 {} 0 }
do_execsql_test view-30.2 {
PRAGMA table_info = t2;
} { 0 a INT 0 {} 0 1 b TEXT 0 {} 0 }
}
finish_test finish_test