b16c2980b3
FossilOrigin-Name: eb64d106551718467e0f6c6b53695410bf4c566901008e4cda8580d0f7efa7b0
1910 lines
76 KiB
JavaScript
1910 lines
76 KiB
JavaScript
/*
|
|
2022-07-22
|
|
|
|
The author disclaims copyright to this source code. In place of a
|
|
legal notice, here is a blessing:
|
|
|
|
* May you do good and not evil.
|
|
* May you find forgiveness for yourself and forgive others.
|
|
* May you share freely, never taking more than you give.
|
|
|
|
***********************************************************************
|
|
|
|
This file glues together disparate pieces of JS which are loaded in
|
|
previous steps of the sqlite3-api.js bootstrapping process:
|
|
sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It
|
|
initializes the main API pieces so that the downstream components
|
|
(e.g. sqlite3-api-oo1.js) have all of the infrastructure that they
|
|
need.
|
|
*/
|
|
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
|
'use strict';
|
|
const toss = (...args)=>{throw new Error(args.join(' '))};
|
|
const toss3 = sqlite3.SQLite3Error.toss;
|
|
const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util;
|
|
globalThis.WhWasmUtilInstaller(wasm);
|
|
delete globalThis.WhWasmUtilInstaller;
|
|
|
|
if(0){
|
|
/**
|
|
Please keep this block around as a maintenance reminder
|
|
that we cannot rely on this type of check.
|
|
|
|
This block fails on Safari, per a report at
|
|
https://sqlite.org/forum/forumpost/e5b20e1feb.
|
|
|
|
It turns out that what Safari serves from the indirect function
|
|
table (e.g. wasm.functionEntry(X)) is anonymous functions which
|
|
wrap the WASM functions, rather than returning the WASM
|
|
functions themselves. That means comparison of such functions
|
|
is useless for determining whether or not we have a specific
|
|
function from wasm.exports. i.e. if function X is indirection
|
|
function table entry N then wasm.exports.X is not equal to
|
|
wasm.functionEntry(N) in Safari, despite being so in the other
|
|
browsers.
|
|
*/
|
|
/**
|
|
Find a mapping for SQLITE_WASM_DEALLOC, which the API
|
|
guarantees is a WASM pointer to the same underlying function as
|
|
wasm.dealloc() (noting that wasm.dealloc() is permitted to be a
|
|
JS wrapper around the WASM function). There is unfortunately no
|
|
O(1) algorithm for finding this pointer: we have to walk the
|
|
WASM indirect function table to find it. However, experience
|
|
indicates that that particular function is always very close to
|
|
the front of the table (it's been entry #3 in all relevant
|
|
tests).
|
|
*/
|
|
const dealloc = wasm.exports[sqlite3.config.deallocExportName];
|
|
const nFunc = wasm.functionTable().length;
|
|
let i;
|
|
for(i = 0; i < nFunc; ++i){
|
|
const e = wasm.functionEntry(i);
|
|
if(dealloc === e){
|
|
capi.SQLITE_WASM_DEALLOC = i;
|
|
break;
|
|
}
|
|
}
|
|
if(dealloc !== wasm.functionEntry(capi.SQLITE_WASM_DEALLOC)){
|
|
toss("Internal error: cannot find function pointer for SQLITE_WASM_DEALLOC.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
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_auto_extension() has a hand-written binding. */
|
|
/* sqlite3_bind_blob() and sqlite3_bind_text() have hand-written
|
|
bindings to permit more flexible inputs. */
|
|
["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_parameter_name", "string", "sqlite3_stmt*", "int"],
|
|
["sqlite3_bind_pointer", "int",
|
|
"sqlite3_stmt*", "int", "*", "string:static", "*"],
|
|
["sqlite3_busy_handler","int", [
|
|
"sqlite3*",
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
signature: 'i(pi)',
|
|
contextKey: (argv,argIndex)=>argv[0/* sqlite3* */]
|
|
}),
|
|
"*"
|
|
]],
|
|
["sqlite3_busy_timeout","int", "sqlite3*", "int"],
|
|
/* sqlite3_cancel_auto_extension() has a hand-written binding. */
|
|
/* sqlite3_close_v2() is implemented by hand to perform some
|
|
extra work. */
|
|
["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_decltype", "string", "sqlite3_stmt*", "int"],
|
|
["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_commit_hook", "void*", [
|
|
"sqlite3*",
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'sqlite3_commit_hook',
|
|
signature: 'i(p)',
|
|
contextKey: (argv)=>argv[0/* sqlite3* */]
|
|
}),
|
|
'*'
|
|
]],
|
|
["sqlite3_compileoption_get", "string", "int"],
|
|
["sqlite3_compileoption_used", "int", "string"],
|
|
["sqlite3_complete", "int", "string:flexible"],
|
|
["sqlite3_context_db_handle", "sqlite3*", "sqlite3_context*"],
|
|
|
|
/* 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_readonly", "int", "sqlite3*", "string"],
|
|
["sqlite3_db_status", "int", "sqlite3*", "int", "*", "*", "int"],
|
|
["sqlite3_errcode", "int", "sqlite3*"],
|
|
["sqlite3_errmsg", "string", "sqlite3*"],
|
|
["sqlite3_error_offset", "int", "sqlite3*"],
|
|
["sqlite3_errstr", "string", "int"],
|
|
["sqlite3_exec", "int", [
|
|
"sqlite3*", "string:flexible",
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
signature: 'i(pipp)',
|
|
bindScope: 'transient',
|
|
callProxy: (callback)=>{
|
|
let aNames;
|
|
return (pVoid, nCols, pColVals, pColNames)=>{
|
|
try {
|
|
const aVals = wasm.cArgvToJs(nCols, pColVals);
|
|
if(!aNames) aNames = wasm.cArgvToJs(nCols, pColNames);
|
|
return callback(aVals, aNames) | 0;
|
|
}catch(e){
|
|
/* If we set the db error state here, the higher-level
|
|
exec() call replaces it with its own, so we have no way
|
|
of reporting the exception message except the console. We
|
|
must not propagate exceptions through the C API. Though
|
|
we make an effort to report OOM here, sqlite3_exec()
|
|
translates that into SQLITE_ABORT as well. */
|
|
return e.resultCode || capi.SQLITE_ERROR;
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
"*", "**"
|
|
]],
|
|
["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_autocommit", "int", "sqlite3*"],
|
|
["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"],
|
|
["sqlite3_initialize", undefined],
|
|
["sqlite3_interrupt", undefined, "sqlite3*"],
|
|
["sqlite3_is_interrupted", "int", "sqlite3*"],
|
|
["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: (argv,argIndex)=>argv[0/* sqlite3* */]
|
|
}), "*"
|
|
]],
|
|
["sqlite3_realloc", "*","*","int"],
|
|
["sqlite3_reset", "int", "sqlite3_stmt*"],
|
|
/* sqlite3_reset_auto_extension() has a hand-written binding. */
|
|
["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_rollback_hook", "void*", [
|
|
"sqlite3*",
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'sqlite3_rollback_hook',
|
|
signature: 'v(p)',
|
|
contextKey: (argv)=>argv[0/* sqlite3* */]
|
|
}),
|
|
'*'
|
|
]],
|
|
["sqlite3_set_auxdata", undefined, [
|
|
"sqlite3_context*", "int", "*",
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xDestroyAuxData',
|
|
signature: 'v(*)',
|
|
contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */]
|
|
})
|
|
]],
|
|
["sqlite3_shutdown", undefined],
|
|
["sqlite3_sourceid", "string"],
|
|
["sqlite3_sql", "string", "sqlite3_stmt*"],
|
|
["sqlite3_status", "int", "int", "*", "*", "int"],
|
|
["sqlite3_step", "int", "sqlite3_stmt*"],
|
|
["sqlite3_stmt_busy", "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: (argv,argIndex)=>argv[0/* sqlite3* */]
|
|
}),
|
|
"*"
|
|
]],
|
|
["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( !!wasm.exports.sqlite3_stmt_explain ){
|
|
wasm.bindingSignatures.push(
|
|
["sqlite3_stmt_explain", "int", "sqlite3_stmt*", "int"],
|
|
["sqlite3_stmt_isexplain", "int", "sqlite3_stmt*"]
|
|
);
|
|
}
|
|
|
|
if( !!wasm.exports.sqlite3_set_authorizer ){
|
|
wasm.bindingSignatures.push(
|
|
["sqlite3_set_authorizer", "int", [
|
|
"sqlite3*",
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: "sqlite3_set_authorizer::xAuth",
|
|
signature: "i(pi"+"ssss)",
|
|
contextKey: (argv, argIndex)=>argv[0/*(sqlite3*)*/],
|
|
callProxy: (callback)=>{
|
|
return (pV, iCode, s0, s1, s2, s3)=>{
|
|
try{
|
|
s0 = s0 && wasm.cstrToJs(s0); s1 = s1 && wasm.cstrToJs(s1);
|
|
s2 = s2 && wasm.cstrToJs(s2); s3 = s3 && wasm.cstrToJs(s3);
|
|
return callback(pV, iCode, s0, s1, s2, s3) || 0;
|
|
}catch(e){
|
|
return e.resultCode || capi.SQLITE_ERROR;
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
"*"/*pUserData*/
|
|
]]
|
|
);
|
|
}/* sqlite3_set_authorizer() */
|
|
|
|
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*"]);
|
|
}
|
|
|
|
//#if enable-see
|
|
if(wasm.exports.sqlite3_key_v2 instanceof Function){
|
|
/**
|
|
This code is capable of using an SEE build but note that an SEE
|
|
WASM build is generally incompatible with SEE's license
|
|
conditions. It is permitted for use internally in organizations
|
|
which have licensed SEE, but not for public sites because
|
|
exposing an SEE build of sqlite3.wasm effectively provides all
|
|
clients with a working copy of the commercial SEE code.
|
|
*/
|
|
wasm.bindingSignatures.push(
|
|
["sqlite3_key", "int", "sqlite3*", "string", "int"],
|
|
["sqlite3_key_v2","int","sqlite3*","string","*","int"],
|
|
["sqlite3_rekey", "int", "sqlite3*", "string", "int"],
|
|
["sqlite3_rekey_v2", "int", "sqlite3*", "string", "*", "int"],
|
|
["sqlite3_activate_see", undefined, "string"]
|
|
);
|
|
}
|
|
//#endif enable-see
|
|
|
|
/**
|
|
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.
|
|
(That said: we never actually build without BigInt support,
|
|
and such builds are untested.)
|
|
|
|
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_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. Because
|
|
of this, the canonical builds of sqlite3.wasm/js guarantee that
|
|
sqlite3.wasm.alloc() and friends use those allocators. Custom builds
|
|
may not guarantee that, however. */,
|
|
["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_serialize","*", "sqlite3*", "string", "*", "int"],
|
|
["sqlite3_set_last_insert_rowid", undefined, ["sqlite3*", "i64"]],
|
|
["sqlite3_status64", "int", "int", "*", "*", "int"],
|
|
["sqlite3_total_changes64", "i64", ["sqlite3*"]],
|
|
["sqlite3_update_hook", "*", [
|
|
"sqlite3*",
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'sqlite3_update_hook',
|
|
signature: "v(iippj)",
|
|
contextKey: (argv)=>argv[0/* sqlite3* */],
|
|
callProxy: (callback)=>{
|
|
return (p,op,z0,z1,rowid)=>{
|
|
callback(p, op, wasm.cstrToJs(z0), wasm.cstrToJs(z1), rowid);
|
|
};
|
|
}
|
|
}),
|
|
"*"
|
|
]],
|
|
["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]],
|
|
["sqlite3_value_int64","i64", "sqlite3_value*"]
|
|
];
|
|
|
|
if( wasm.bigIntEnabled && !!wasm.exports.sqlite3_declare_vtab ){
|
|
wasm.bindingSignatures.int64.push(
|
|
["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_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", "**"]
|
|
);
|
|
}/* virtual table APIs */
|
|
|
|
if(wasm.bigIntEnabled && !!wasm.exports.sqlite3_preupdate_hook){
|
|
wasm.bindingSignatures.int64.push(
|
|
["sqlite3_preupdate_blobwrite", "int", "sqlite3*"],
|
|
["sqlite3_preupdate_count", "int", "sqlite3*"],
|
|
["sqlite3_preupdate_depth", "int", "sqlite3*"],
|
|
["sqlite3_preupdate_hook", "*", [
|
|
"sqlite3*",
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'sqlite3_preupdate_hook',
|
|
signature: "v(ppippjj)",
|
|
contextKey: (argv)=>argv[0/* sqlite3* */],
|
|
callProxy: (callback)=>{
|
|
return (p,db,op,zDb,zTbl,iKey1,iKey2)=>{
|
|
callback(p, db, op, wasm.cstrToJs(zDb), wasm.cstrToJs(zTbl),
|
|
iKey1, iKey2);
|
|
};
|
|
}
|
|
}),
|
|
"*"
|
|
]],
|
|
["sqlite3_preupdate_new", "int", ["sqlite3*", "int", "**"]],
|
|
["sqlite3_preupdate_old", "int", ["sqlite3*", "int", "**"]]
|
|
);
|
|
} /* preupdate API */
|
|
|
|
// Add session/changeset APIs...
|
|
if(wasm.bigIntEnabled
|
|
&& !!wasm.exports.sqlite3changegroup_add
|
|
&& !!wasm.exports.sqlite3session_create
|
|
&& !!wasm.exports.sqlite3_preupdate_hook /* required by the session API */){
|
|
/**
|
|
FuncPtrAdapter options for session-related callbacks with the
|
|
native signature "i(ps)". This proxy converts the 2nd argument
|
|
from a C string to a JS string before passing the arguments on
|
|
to the client-provided JS callback.
|
|
*/
|
|
const __ipsProxy = {
|
|
signature: 'i(ps)',
|
|
callProxy:(callback)=>{
|
|
return (p,s)=>{
|
|
try{return callback(p, wasm.cstrToJs(s)) | 0}
|
|
catch(e){return e.resultCode || capi.SQLITE_ERROR}
|
|
}
|
|
}
|
|
};
|
|
|
|
wasm.bindingSignatures.int64.push(...[
|
|
['sqlite3changegroup_add', 'int', ['sqlite3_changegroup*', 'int', 'void*']],
|
|
['sqlite3changegroup_add_strm', 'int', [
|
|
'sqlite3_changegroup*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
|
|
}),
|
|
'void*'
|
|
]],
|
|
['sqlite3changegroup_delete', undefined, ['sqlite3_changegroup*']],
|
|
['sqlite3changegroup_new', 'int', ['**']],
|
|
['sqlite3changegroup_output', 'int', ['sqlite3_changegroup*', 'int*', '**']],
|
|
['sqlite3changegroup_output_strm', 'int', [
|
|
'sqlite3_changegroup*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient'
|
|
}),
|
|
'void*'
|
|
]],
|
|
['sqlite3changeset_apply', 'int', [
|
|
'sqlite3*', 'int', 'void*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xFilter', bindScope: 'transient', ...__ipsProxy
|
|
}),
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
|
|
}),
|
|
'void*'
|
|
]],
|
|
['sqlite3changeset_apply_strm', 'int', [
|
|
'sqlite3*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
|
|
}),
|
|
'void*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xFilter', bindScope: 'transient', ...__ipsProxy
|
|
}),
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
|
|
}),
|
|
'void*'
|
|
]],
|
|
['sqlite3changeset_apply_v2', 'int', [
|
|
'sqlite3*', 'int', 'void*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xFilter', bindScope: 'transient', ...__ipsProxy
|
|
}),
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
|
|
}),
|
|
'void*', '**', 'int*', 'int'
|
|
|
|
]],
|
|
['sqlite3changeset_apply_v2_strm', 'int', [
|
|
'sqlite3*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
|
|
}),
|
|
'void*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xFilter', bindScope: 'transient', ...__ipsProxy
|
|
}),
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
|
|
}),
|
|
'void*', '**', 'int*', 'int'
|
|
]],
|
|
['sqlite3changeset_concat', 'int', ['int','void*', 'int', 'void*', 'int*', '**']],
|
|
['sqlite3changeset_concat_strm', 'int', [
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xInputA', signature: 'i(ppp)', bindScope: 'transient'
|
|
}),
|
|
'void*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xInputB', signature: 'i(ppp)', bindScope: 'transient'
|
|
}),
|
|
'void*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient'
|
|
}),
|
|
'void*'
|
|
]],
|
|
['sqlite3changeset_conflict', 'int', ['sqlite3_changeset_iter*', 'int', '**']],
|
|
['sqlite3changeset_finalize', 'int', ['sqlite3_changeset_iter*']],
|
|
['sqlite3changeset_fk_conflicts', 'int', ['sqlite3_changeset_iter*', 'int*']],
|
|
['sqlite3changeset_invert', 'int', ['int', 'void*', 'int*', '**']],
|
|
['sqlite3changeset_invert_strm', 'int', [
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
|
|
}),
|
|
'void*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient'
|
|
}),
|
|
'void*'
|
|
]],
|
|
['sqlite3changeset_new', 'int', ['sqlite3_changeset_iter*', 'int', '**']],
|
|
['sqlite3changeset_next', 'int', ['sqlite3_changeset_iter*']],
|
|
['sqlite3changeset_old', 'int', ['sqlite3_changeset_iter*', 'int', '**']],
|
|
['sqlite3changeset_op', 'int', [
|
|
'sqlite3_changeset_iter*', '**', 'int*', 'int*','int*'
|
|
]],
|
|
['sqlite3changeset_pk', 'int', ['sqlite3_changeset_iter*', '**', 'int*']],
|
|
['sqlite3changeset_start', 'int', ['**', 'int', '*']],
|
|
['sqlite3changeset_start_strm', 'int', [
|
|
'**',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
|
|
}),
|
|
'void*'
|
|
]],
|
|
['sqlite3changeset_start_v2', 'int', ['**', 'int', '*', 'int']],
|
|
['sqlite3changeset_start_v2_strm', 'int', [
|
|
'**',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
|
|
}),
|
|
'void*', 'int'
|
|
]],
|
|
['sqlite3session_attach', 'int', ['sqlite3_session*', 'string']],
|
|
['sqlite3session_changeset', 'int', ['sqlite3_session*', 'int*', '**']],
|
|
['sqlite3session_changeset_size', 'i64', ['sqlite3_session*']],
|
|
['sqlite3session_changeset_strm', 'int', [
|
|
'sqlite3_session*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient'
|
|
}),
|
|
'void*'
|
|
]],
|
|
['sqlite3session_config', 'int', ['int', 'void*']],
|
|
['sqlite3session_create', 'int', ['sqlite3*', 'string', '**']],
|
|
//sqlite3session_delete() is bound manually
|
|
['sqlite3session_diff', 'int', ['sqlite3_session*', 'string', 'string', '**']],
|
|
['sqlite3session_enable', 'int', ['sqlite3_session*', 'int']],
|
|
['sqlite3session_indirect', 'int', ['sqlite3_session*', 'int']],
|
|
['sqlite3session_isempty', 'int', ['sqlite3_session*']],
|
|
['sqlite3session_memory_used', 'i64', ['sqlite3_session*']],
|
|
['sqlite3session_object_config', 'int', ['sqlite3_session*', 'int', 'void*']],
|
|
['sqlite3session_patchset', 'int', ['sqlite3_session*', '*', '**']],
|
|
['sqlite3session_patchset_strm', 'int', [
|
|
'sqlite3_session*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient'
|
|
}),
|
|
'void*'
|
|
]],
|
|
['sqlite3session_table_filter', undefined, [
|
|
'sqlite3_session*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
name: 'xFilter', ...__ipsProxy,
|
|
contextKey: (argv,argIndex)=>argv[0/* (sqlite3_session*) */]
|
|
}),
|
|
'*'
|
|
]]
|
|
]);
|
|
}/*session/changeset APIs*/
|
|
|
|
/**
|
|
Functions which are intended solely for API-internal use by the
|
|
WASM components, not client code. These get installed into
|
|
sqlite3.util. Some of them get exposed to clients via variants
|
|
in sqlite3_js_...().
|
|
|
|
2024-01-11: these were renamed, with two underscores in the
|
|
prefix, to ensure that clients do not accidentally depend on
|
|
them. They have always been documented as internal-use-only, so
|
|
no clients "should" be depending on the old names.
|
|
*/
|
|
wasm.bindingSignatures.wasmInternal = [
|
|
["sqlite3__wasm_db_reset", "int", "sqlite3*"],
|
|
["sqlite3__wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"],
|
|
[/* DO NOT USE. This is deprecated since 2023-08-11 because it can
|
|
trigger assert() in debug builds when used with file sizes
|
|
which are not sizes to a multiple of a valid db page size. */
|
|
"sqlite3__wasm_vfs_create_file", "int", "sqlite3_vfs*","string","*", "int"
|
|
],
|
|
["sqlite3__wasm_posix_create_file", "int", "string","*", "int"],
|
|
["sqlite3__wasm_vfs_unlink", "int", "sqlite3_vfs*","string"],
|
|
["sqlite3__wasm_qfmt_token","string:dealloc", "string","int"]
|
|
];
|
|
|
|
/**
|
|
Install JS<->C struct bindings for the non-opaque struct types we
|
|
need... */
|
|
sqlite3.StructBinder = globalThis.Jaccwabyt({
|
|
heap: 0 ? wasm.memory : wasm.heap8u,
|
|
alloc: wasm.alloc,
|
|
dealloc: wasm.dealloc,
|
|
bigIntEnabled: wasm.bigIntEnabled,
|
|
memberPrefix: /* Never change this: this prefix is baked into any
|
|
amount of code and client-facing docs. */ '$'
|
|
});
|
|
delete globalThis.Jaccwabyt;
|
|
|
|
{// wasm.xWrap() bindings...
|
|
|
|
/* Convert Arrays and certain TypedArrays to strings for
|
|
'string:flexible'-type arguments */
|
|
const __xString = wasm.xWrap.argAdapter('string');
|
|
wasm.xWrap.argAdapter(
|
|
'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 || (this[v] = wasm.allocCString(v));
|
|
}.bind(Object.create(null))
|
|
);
|
|
|
|
/**
|
|
Add some descriptive xWrap() aliases for '*' intended to (A)
|
|
initially improve readability/correctness of
|
|
wasm.bindingSignatures and (B) provide automatic conversion
|
|
from higher-level representations, e.g. capi.sqlite3_vfs to
|
|
`sqlite3_vfs*` via capi.sqlite3_vfs.pointer.
|
|
*/
|
|
const __xArgPtr = wasm.xWrap.argAdapter('*');
|
|
const nilType = function(){
|
|
/*a class which no value can ever be an instance of*/
|
|
};
|
|
wasm.xWrap.argAdapter('sqlite3_filename', __xArgPtr)
|
|
('sqlite3_context*', __xArgPtr)
|
|
('sqlite3_value*', __xArgPtr)
|
|
('void*', __xArgPtr)
|
|
('sqlite3_changegroup*', __xArgPtr)
|
|
('sqlite3_changeset_iter*', __xArgPtr)
|
|
('sqlite3_session*', __xArgPtr)
|
|
('sqlite3_stmt*', (v)=>
|
|
__xArgPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType))
|
|
? v.pointer : v))
|
|
('sqlite3*', (v)=>
|
|
__xArgPtr((v instanceof (sqlite3?.oo1?.DB || nilType))
|
|
? v.pointer : v))
|
|
/**
|
|
`sqlite3_vfs*`:
|
|
|
|
- v is-a string: use the result of sqlite3_vfs_find(v) but
|
|
throw if it returns 0.
|
|
- v is-a capi.sqlite3_vfs: use v.pointer.
|
|
- Else return the same as the `'*'` argument conversion.
|
|
*/
|
|
('sqlite3_vfs*', (v)=>{
|
|
if('string'===typeof v){
|
|
/* A NULL sqlite3_vfs pointer will be treated as the default
|
|
VFS in many contexts. We specifically do not want that
|
|
behavior here. */
|
|
return capi.sqlite3_vfs_find(v)
|
|
|| sqlite3.SQLite3Error.toss(
|
|
capi.SQLITE_NOTFOUND,
|
|
"Unknown sqlite3_vfs name:", v
|
|
);
|
|
}
|
|
return __xArgPtr((v instanceof (capi.sqlite3_vfs || nilType))
|
|
? v.pointer : v);
|
|
});
|
|
if( wasm.exports.sqlite3_declare_vtab ){
|
|
wasm.xWrap.argAdapter('sqlite3_index_info*', (v)=>
|
|
__xArgPtr((v instanceof (capi.sqlite3_index_info || nilType))
|
|
? v.pointer : v))
|
|
('sqlite3_module*', (v)=>
|
|
__xArgPtr((v instanceof (capi.sqlite3_module || nilType))
|
|
? v.pointer : v)
|
|
);
|
|
}
|
|
|
|
const __xRcPtr = wasm.xWrap.resultAdapter('*');
|
|
wasm.xWrap.resultAdapter('sqlite3*', __xRcPtr)
|
|
('sqlite3_context*', __xRcPtr)
|
|
('sqlite3_stmt*', __xRcPtr)
|
|
('sqlite3_value*', __xRcPtr)
|
|
('sqlite3_vfs*', __xRcPtr)
|
|
('void*', __xRcPtr);
|
|
|
|
/**
|
|
Populate api object with sqlite3_...() by binding the "raw" wasm
|
|
exports into type-converting proxies using wasm.xWrap().
|
|
*/
|
|
if(0 === wasm.exports.sqlite3_step.length){
|
|
/* This environment wraps exports in nullary functions, which means
|
|
we must disable the arg-count validation we otherwise perform
|
|
on the wrappers. */
|
|
wasm.xWrap.doArgcCheck = false;
|
|
sqlite3.config.warn(
|
|
"Disabling sqlite3.wasm.xWrap.doArgcCheck due to environmental quirks."
|
|
);
|
|
}
|
|
for(const e of wasm.bindingSignatures){
|
|
capi[e[0]] = wasm.xWrap.apply(null, e);
|
|
}
|
|
for(const e of wasm.bindingSignatures.wasmInternal){
|
|
util[e[0]] = wasm.xWrap.apply(null, e);
|
|
}
|
|
|
|
/* For C API functions which cannot work properly unless
|
|
wasm.bigIntEnabled is true, install a bogus impl which throws
|
|
if called when bigIntEnabled is false. The alternative would be
|
|
to elide these functions altogether, which seems likely to
|
|
cause more confusion. */
|
|
const fI64Disabled = function(fname){
|
|
return ()=>toss(fname+"() is unavailable due to lack",
|
|
"of BigInt support in this build.");
|
|
};
|
|
for(const e of wasm.bindingSignatures.int64){
|
|
capi[e[0]] = wasm.bigIntEnabled
|
|
? wasm.xWrap.apply(null, e)
|
|
: fI64Disabled(e[0]);
|
|
}
|
|
|
|
/* There's no need to expose bindingSignatures to clients,
|
|
implicitly making it part of the public interface. */
|
|
delete wasm.bindingSignatures;
|
|
|
|
if(wasm.exports.sqlite3__wasm_db_error){
|
|
const __db_err = wasm.xWrap(
|
|
'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 ignored 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 pDb ? __db_err(pDb, resultCode, message) : resultCode;
|
|
};
|
|
}else{
|
|
util.sqlite3__wasm_db_error = function(pDb,errCode,msg){
|
|
console.warn("sqlite3__wasm_db_error() is not exported.",arguments);
|
|
return errCode;
|
|
};
|
|
}
|
|
}/*xWrap() bindings*/
|
|
|
|
{/* Import C-level constants and structs... */
|
|
const cJson = wasm.xCall('sqlite3__wasm_enum_json');
|
|
if(!cJson){
|
|
toss("Maintenance required: increase sqlite3__wasm_enum_json()'s",
|
|
"static buffer size!");
|
|
}
|
|
//console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
|
|
wasm.ctype = JSON.parse(wasm.cstrToJs(cJson));
|
|
// Groups of SQLITE_xyz macros...
|
|
const defineGroups = ['access', 'authorizer',
|
|
'blobFinalizers', 'changeset',
|
|
'config', 'dataTypes',
|
|
'dbConfig', 'dbStatus',
|
|
'encodings', 'fcntl', 'flock', 'ioCap',
|
|
'limits', 'openFlags',
|
|
'prepareFlags', 'resultCodes',
|
|
'sqlite3Status',
|
|
'stmtStatus', 'syncFlags',
|
|
'trace', 'txnState', 'udfFlags',
|
|
'version' ];
|
|
if(wasm.bigIntEnabled){
|
|
defineGroups.push('serialize', 'session', 'vtab');
|
|
}
|
|
for(const t of defineGroups){
|
|
for(const e of Object.entries(wasm.ctype[t])){
|
|
// ^^^ [k,v] there triggers a buggy code transformation via
|
|
// one of the Emscripten-driven optimizers.
|
|
capi[e[0]] = e[1];
|
|
}
|
|
}
|
|
if(!wasm.functionEntry(capi.SQLITE_WASM_DEALLOC)){
|
|
toss("Internal error: cannot resolve exported function",
|
|
"entry SQLITE_WASM_DEALLOC (=="+capi.SQLITE_WASM_DEALLOC+").");
|
|
}
|
|
const __rcMap = Object.create(null);
|
|
for(const t of ['resultCodes']){
|
|
for(const e of Object.entries(wasm.ctype[t])){
|
|
__rcMap[e[1]] = e[0];
|
|
}
|
|
}
|
|
/**
|
|
For the given integer, returns the SQLITE_xxx result code as a
|
|
string, or undefined if no such mapping is found.
|
|
*/
|
|
capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc];
|
|
/* Bind all registered C-side structs... */
|
|
const notThese = Object.assign(Object.create(null),{
|
|
// For each struct to NOT register, map its name to 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
|
|
});
|
|
for(const s of wasm.ctype.structs){
|
|
if(!notThese[s.name]){
|
|
capi[s.name] = sqlite3.StructBinder(s);
|
|
}
|
|
}
|
|
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 = wasm.xWrap(
|
|
'sqlite3__wasm_vtab_config','int',[
|
|
'sqlite3*', 'int', 'int']
|
|
);
|
|
}/* end vtab-related setup */
|
|
}/*end C constant and struct imports*/
|
|
|
|
/**
|
|
Internal helper to assist in validating call argument counts in
|
|
the hand-written sqlite3_xyz() wrappers. We do this only for
|
|
consistency with non-special-case wrappings.
|
|
*/
|
|
const __dbArgcMismatch = (pDb,f,n)=>{
|
|
return util.sqlite3__wasm_db_error(pDb, capi.SQLITE_MISUSE,
|
|
f+"() requires "+n+" argument"+
|
|
(1===n?"":'s')+".");
|
|
};
|
|
|
|
/** Code duplication reducer for functions which take an encoding
|
|
argument and require SQLITE_UTF8. Sets the db error code to
|
|
SQLITE_FORMAT and returns that code. */
|
|
const __errEncoding = (pDb)=>{
|
|
return util.sqlite3__wasm_db_error(
|
|
pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding."
|
|
);
|
|
};
|
|
|
|
/**
|
|
__dbCleanupMap is infrastructure for recording registration of
|
|
UDFs and collations so that sqlite3_close_v2() can clean up any
|
|
automated JS-to-WASM function conversions installed by those.
|
|
*/
|
|
const __argPDb = (pDb)=>wasm.xWrap.argAdapter('sqlite3*')(pDb);
|
|
const __argStr = (str)=>wasm.isPtr(str) ? wasm.cstrToJs(str) : str;
|
|
const __dbCleanupMap = function(
|
|
pDb, mode/*0=remove, >0=create if needed, <0=do not create if missing*/
|
|
){
|
|
pDb = __argPDb(pDb);
|
|
let m = this.dbMap.get(pDb);
|
|
if(!mode){
|
|
this.dbMap.delete(pDb);
|
|
return m;
|
|
}else if(!m && mode>0){
|
|
this.dbMap.set(pDb, (m = Object.create(null)));
|
|
}
|
|
return m;
|
|
}.bind(Object.assign(Object.create(null),{
|
|
dbMap: new Map
|
|
}));
|
|
|
|
__dbCleanupMap.addCollation = function(pDb, name){
|
|
const m = __dbCleanupMap(pDb, 1);
|
|
if(!m.collation) m.collation = new Set;
|
|
m.collation.add(__argStr(name).toLowerCase());
|
|
};
|
|
|
|
__dbCleanupMap._addUDF = function(pDb, name, arity, map){
|
|
/* Map UDF name to a Set of arity values */
|
|
name = __argStr(name).toLowerCase();
|
|
let u = map.get(name);
|
|
if(!u) map.set(name, (u = new Set));
|
|
u.add((arity<0) ? -1 : arity);
|
|
};
|
|
|
|
__dbCleanupMap.addFunction = function(pDb, name, arity){
|
|
const m = __dbCleanupMap(pDb, 1);
|
|
if(!m.udf) m.udf = new Map;
|
|
this._addUDF(pDb, name, arity, m.udf);
|
|
};
|
|
|
|
__dbCleanupMap.addWindowFunc = function(pDb, name, arity){
|
|
const m = __dbCleanupMap(pDb, 1);
|
|
if(!m.wudf) m.wudf = new Map;
|
|
this._addUDF(pDb, name, arity, m.wudf);
|
|
};
|
|
|
|
/**
|
|
Intended to be called _only_ from sqlite3_close_v2(),
|
|
passed its non-0 db argument.
|
|
|
|
This function frees up certain automatically-installed WASM
|
|
function bindings which were installed on behalf of the given db,
|
|
as those may otherwise leak.
|
|
|
|
Notable caveat: this is only ever run via
|
|
sqlite3.capi.sqlite3_close_v2(). If a client, for whatever
|
|
reason, uses sqlite3.wasm.exports.sqlite3_close_v2() (the
|
|
function directly exported from WASM), this cleanup will not
|
|
happen.
|
|
|
|
This is not a silver bullet for avoiding automation-related
|
|
leaks but represents "an honest effort."
|
|
|
|
The issue being addressed here is covered at:
|
|
|
|
https://sqlite.org/wasm/doc/trunk/api-c-style.md#convert-func-ptr
|
|
*/
|
|
__dbCleanupMap.cleanup = function(pDb){
|
|
pDb = __argPDb(pDb);
|
|
//wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false;
|
|
/**
|
|
Installing NULL functions in the C API will remove those
|
|
bindings. The FuncPtrAdapter which sits between us and the C
|
|
API will also treat that as an opportunity to
|
|
wasm.uninstallFunction() any WASM function bindings it has
|
|
installed for pDb.
|
|
*/
|
|
const closeArgs = [pDb];
|
|
for(const name of [
|
|
'sqlite3_busy_handler',
|
|
'sqlite3_commit_hook',
|
|
'sqlite3_preupdate_hook',
|
|
'sqlite3_progress_handler',
|
|
'sqlite3_rollback_hook',
|
|
'sqlite3_set_authorizer',
|
|
'sqlite3_trace_v2',
|
|
'sqlite3_update_hook'
|
|
]) {
|
|
const x = wasm.exports[name];
|
|
if( !x ){
|
|
/* assume it was built without this API */
|
|
continue;
|
|
}
|
|
closeArgs.length = x.length/*==argument count*/
|
|
/* recall that undefined entries translate to 0 when passed to
|
|
WASM. */;
|
|
try{ capi[name](...closeArgs) }
|
|
catch(e){
|
|
sqlite3.config.warn("close-time call of",name+"(",closeArgs,") threw:",e);
|
|
}
|
|
}
|
|
const m = __dbCleanupMap(pDb, 0);
|
|
if(!m) return;
|
|
if(m.collation){
|
|
for(const name of m.collation){
|
|
try{
|
|
capi.sqlite3_create_collation_v2(
|
|
pDb, name, capi.SQLITE_UTF8, 0, 0, 0
|
|
);
|
|
}catch(e){
|
|
/*ignored*/
|
|
}
|
|
}
|
|
delete m.collation;
|
|
}
|
|
let i;
|
|
for(i = 0; i < 2; ++i){ /* Clean up UDFs... */
|
|
const fmap = i ? m.wudf : m.udf;
|
|
if(!fmap) continue;
|
|
const func = i
|
|
? capi.sqlite3_create_window_function
|
|
: capi.sqlite3_create_function_v2;
|
|
for(const e of fmap){
|
|
const name = e[0], arities = e[1];
|
|
const fargs = [pDb, name, 0/*arity*/, capi.SQLITE_UTF8, 0, 0, 0, 0, 0];
|
|
if(i) fargs.push(0);
|
|
for(const arity of arities){
|
|
try{ fargs[2] = arity; func.apply(null, fargs); }
|
|
catch(e){/*ignored*/}
|
|
}
|
|
arities.clear();
|
|
}
|
|
fmap.clear();
|
|
}
|
|
delete m.udf;
|
|
delete m.wudf;
|
|
}/*__dbCleanupMap.cleanup()*/;
|
|
|
|
{/* Binding of sqlite3_close_v2() */
|
|
const __sqlite3CloseV2 = wasm.xWrap("sqlite3_close_v2", "int", "sqlite3*");
|
|
capi.sqlite3_close_v2 = function(pDb){
|
|
if(1!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_close_v2', 1);
|
|
if(pDb){
|
|
try{__dbCleanupMap.cleanup(pDb)} catch(e){/*ignored*/}
|
|
}
|
|
return __sqlite3CloseV2(pDb);
|
|
};
|
|
}/*sqlite3_close_v2()*/
|
|
|
|
if(capi.sqlite3session_create){
|
|
const __sqlite3SessionDelete = wasm.xWrap(
|
|
'sqlite3session_delete', undefined, ['sqlite3_session*']
|
|
);
|
|
capi.sqlite3session_delete = function(pSession){
|
|
if(1!==arguments.length){
|
|
return __dbArgcMismatch(pDb, 'sqlite3session_delete', 1);
|
|
/* Yes, we're returning a value from a void function. That seems
|
|
like the lesser evil compared to not maintaining arg-count
|
|
consistency as we do with other similar bindings. */
|
|
}
|
|
else if(pSession){
|
|
//wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true;
|
|
capi.sqlite3session_table_filter(pSession, 0, 0);
|
|
}
|
|
__sqlite3SessionDelete(pSession);
|
|
};
|
|
}
|
|
|
|
{/* Bindings for sqlite3_create_collation[_v2]() */
|
|
// contextKey() impl for wasm.xWrap.FuncPtrAdapter
|
|
const contextKey = (argv,argIndex)=>{
|
|
return 'argv['+argIndex+']:'+argv[0/* sqlite3* */]+
|
|
':'+wasm.cstrToJs(argv[1/* collation name */]).toLowerCase()
|
|
};
|
|
const __sqlite3CreateCollationV2 = wasm.xWrap(
|
|
'sqlite3_create_collation_v2', 'int', [
|
|
'sqlite3*', 'string', 'int', '*',
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
/* int(*xCompare)(void*,int,const void*,int,const void*) */
|
|
name: 'xCompare', signature: 'i(pipip)', contextKey
|
|
}),
|
|
new wasm.xWrap.FuncPtrAdapter({
|
|
/* void(*xDestroy(void*) */
|
|
name: 'xDestroy', signature: 'v(p)', contextKey
|
|
})
|
|
]
|
|
);
|
|
|
|
/**
|
|
Works exactly like C's sqlite3_create_collation_v2() except that:
|
|
|
|
1) It returns capi.SQLITE_FORMAT if the 3rd argument contains
|
|
any encoding-related value other than capi.SQLITE_UTF8. No
|
|
other encodings are supported. As a special case, if the
|
|
bottom 4 bits of that argument are 0, SQLITE_UTF8 is
|
|
assumed.
|
|
|
|
2) 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 calls this again with the
|
|
same collation name and a value of 0 or null for the
|
|
the function pointer(s).
|
|
|
|
For consistency with the C API, it requires the same number of
|
|
arguments. It returns capi.SQLITE_MISUSE if passed any other
|
|
argument count.
|
|
|
|
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( 0 === (eTextRep & 0xf) ){
|
|
eTextRep |= capi.SQLITE_UTF8;
|
|
}else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){
|
|
return __errEncoding(pDb);
|
|
}
|
|
try{
|
|
const rc = __sqlite3CreateCollationV2(pDb, zName, eTextRep, pArg, xCompare, xDestroy);
|
|
if(0===rc && xCompare instanceof Function){
|
|
__dbCleanupMap.addCollation(pDb, zName);
|
|
}
|
|
return rc;
|
|
}catch(e){
|
|
return util.sqlite3__wasm_db_error(pDb, e);
|
|
}
|
|
};
|
|
|
|
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*/
|
|
|
|
{/* Special-case handling of sqlite3_create_function_v2()
|
|
and sqlite3_create_window_function(). */
|
|
/** FuncPtrAdapter for contextKey() for sqlite3_create_function()
|
|
and friends. */
|
|
const contextKey = function(argv,argIndex){
|
|
return (
|
|
argv[0/* sqlite3* */]
|
|
+':'+(argv[2/*number of UDF args*/] < 0 ? -1 : argv[2])
|
|
+':'+argIndex/*distinct for each xAbc callback type*/
|
|
+':'+wasm.cstrToJs(argv[1]).toLowerCase()
|
|
)
|
|
};
|
|
|
|
/**
|
|
JS proxies for the various sqlite3_create[_window]_function()
|
|
callbacks, structured in a form usable by wasm.xWrap.FuncPtrAdapter.
|
|
*/
|
|
const __cfProxy = Object.assign(Object.create(null), {
|
|
xInverseAndStep: {
|
|
signature:'v(pip)', contextKey,
|
|
callProxy: (callback)=>{
|
|
return (pCtx, argc, pArgv)=>{
|
|
try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) }
|
|
catch(e){ capi.sqlite3_result_error_js(pCtx, e) }
|
|
};
|
|
}
|
|
},
|
|
xFinalAndValue: {
|
|
signature:'v(p)', contextKey,
|
|
callProxy: (callback)=>{
|
|
return (pCtx)=>{
|
|
try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) }
|
|
catch(e){ capi.sqlite3_result_error_js(pCtx, e) }
|
|
};
|
|
}
|
|
},
|
|
xFunc: {
|
|
signature:'v(pip)', contextKey,
|
|
callProxy: (callback)=>{
|
|
return (pCtx, argc, pArgv)=>{
|
|
try{
|
|
capi.sqlite3_result_js(
|
|
pCtx,
|
|
callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv))
|
|
);
|
|
}catch(e){
|
|
//console.error('xFunc() caught:',e);
|
|
capi.sqlite3_result_error_js(pCtx, e);
|
|
}
|
|
};
|
|
}
|
|
},
|
|
xDestroy: {
|
|
signature:'v(p)', contextKey,
|
|
//Arguable: a well-behaved destructor doesn't require a proxy.
|
|
callProxy: (callback)=>{
|
|
return (pVoid)=>{
|
|
try{ callback(pVoid) }
|
|
catch(e){ console.error("UDF xDestroy method threw:",e) }
|
|
};
|
|
}
|
|
}
|
|
})/*__cfProxy*/;
|
|
|
|
const __sqlite3CreateFunction = wasm.xWrap(
|
|
"sqlite3_create_function_v2", "int", [
|
|
"sqlite3*", "string"/*funcName*/, "int"/*nArg*/,
|
|
"int"/*eTextRep*/, "*"/*pApp*/,
|
|
new wasm.xWrap.FuncPtrAdapter({name: 'xFunc', ...__cfProxy.xFunc}),
|
|
new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}),
|
|
new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}),
|
|
new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy})
|
|
]
|
|
);
|
|
|
|
const __sqlite3CreateWindowFunction = wasm.xWrap(
|
|
"sqlite3_create_window_function", "int", [
|
|
"sqlite3*", "string"/*funcName*/, "int"/*nArg*/,
|
|
"int"/*eTextRep*/, "*"/*pApp*/,
|
|
new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}),
|
|
new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}),
|
|
new wasm.xWrap.FuncPtrAdapter({name: 'xValue', ...__cfProxy.xFinalAndValue}),
|
|
new wasm.xWrap.FuncPtrAdapter({name: 'xInverse', ...__cfProxy.xInverseAndStep}),
|
|
new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy})
|
|
]
|
|
);
|
|
|
|
/* Documented in the api object's initializer. */
|
|
capi.sqlite3_create_function_v2 = function f(
|
|
pDb, funcName, nArg, eTextRep, pApp,
|
|
xFunc, //void (*xFunc)(sqlite3_context*,int,sqlite3_value**)
|
|
xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**)
|
|
xFinal, //void (*xFinal)(sqlite3_context*)
|
|
xDestroy //void (*xDestroy)(void*)
|
|
){
|
|
if( f.length!==arguments.length ){
|
|
return __dbArgcMismatch(pDb,"sqlite3_create_function_v2",f.length);
|
|
}else if( 0 === (eTextRep & 0xf) ){
|
|
eTextRep |= capi.SQLITE_UTF8;
|
|
}else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){
|
|
return __errEncoding(pDb);
|
|
}
|
|
try{
|
|
const rc = __sqlite3CreateFunction(pDb, funcName, nArg, eTextRep,
|
|
pApp, xFunc, xStep, xFinal, xDestroy);
|
|
if(0===rc && (xFunc instanceof Function
|
|
|| xStep instanceof Function
|
|
|| xFinal instanceof Function
|
|
|| xDestroy instanceof Function)){
|
|
__dbCleanupMap.addFunction(pDb, funcName, nArg);
|
|
}
|
|
return rc;
|
|
}catch(e){
|
|
console.error("sqlite3_create_function_v2() setup threw:",e);
|
|
return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
|
|
}
|
|
};
|
|
|
|
/* Documented in the api object's initializer. */
|
|
capi.sqlite3_create_function = function f(
|
|
pDb, funcName, nArg, eTextRep, pApp,
|
|
xFunc, xStep, xFinal
|
|
){
|
|
return (f.length===arguments.length)
|
|
? capi.sqlite3_create_function_v2(pDb, funcName, nArg, eTextRep,
|
|
pApp, xFunc, xStep, xFinal, 0)
|
|
: __dbArgcMismatch(pDb,"sqlite3_create_function",f.length);
|
|
};
|
|
|
|
/* Documented in the api object's initializer. */
|
|
capi.sqlite3_create_window_function = function f(
|
|
pDb, funcName, nArg, eTextRep, pApp,
|
|
xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**)
|
|
xFinal, //void (*xFinal)(sqlite3_context*)
|
|
xValue, //void (*xValue)(sqlite3_context*)
|
|
xInverse,//void (*xInverse)(sqlite3_context*,int,sqlite3_value**)
|
|
xDestroy //void (*xDestroy)(void*)
|
|
){
|
|
if( f.length!==arguments.length ){
|
|
return __dbArgcMismatch(pDb,"sqlite3_create_window_function",f.length);
|
|
}else if( 0 === (eTextRep & 0xf) ){
|
|
eTextRep |= capi.SQLITE_UTF8;
|
|
}else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){
|
|
return __errEncoding(pDb);
|
|
}
|
|
try{
|
|
const rc = __sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep,
|
|
pApp, xStep, xFinal, xValue,
|
|
xInverse, xDestroy);
|
|
if(0===rc && (xStep instanceof Function
|
|
|| xFinal instanceof Function
|
|
|| xValue instanceof Function
|
|
|| xInverse instanceof Function
|
|
|| xDestroy instanceof Function)){
|
|
__dbCleanupMap.addWindowFunc(pDb, funcName, nArg);
|
|
}
|
|
return rc;
|
|
}catch(e){
|
|
console.error("sqlite3_create_window_function() setup threw:",e);
|
|
return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
|
|
}
|
|
};
|
|
/**
|
|
A _deprecated_ alias for capi.sqlite3_result_js() which
|
|
predates the addition of that function in the public API.
|
|
*/
|
|
capi.sqlite3_create_function_v2.udfSetResult =
|
|
capi.sqlite3_create_function.udfSetResult =
|
|
capi.sqlite3_create_window_function.udfSetResult = capi.sqlite3_result_js;
|
|
|
|
/**
|
|
A _deprecated_ alias for capi.sqlite3_values_to_js() which
|
|
predates the addition of that function in the public API.
|
|
*/
|
|
capi.sqlite3_create_function_v2.udfConvertArgs =
|
|
capi.sqlite3_create_function.udfConvertArgs =
|
|
capi.sqlite3_create_window_function.udfConvertArgs = capi.sqlite3_values_to_js;
|
|
|
|
/**
|
|
A _deprecated_ alias for capi.sqlite3_result_error_js() which
|
|
predates the addition of that function in the public API.
|
|
*/
|
|
capi.sqlite3_create_function_v2.udfSetError =
|
|
capi.sqlite3_create_function.udfSetError =
|
|
capi.sqlite3_create_window_function.udfSetError = capi.sqlite3_result_error_js;
|
|
|
|
}/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/;
|
|
|
|
{/* Special-case handling of sqlite3_prepare_v2() and
|
|
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 or ArrayBuffer).
|
|
*/
|
|
const __flexiString = (v,n)=>{
|
|
if('string'===typeof v){
|
|
n = -1;
|
|
}else if(util.isSQLableTypedArray(v)){
|
|
n = v.byteLength;
|
|
v = util.typedArrayToString(
|
|
(v instanceof ArrayBuffer) ? new Uint8Array(v) : 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().
|
|
*/
|
|
const __prepare = {
|
|
/**
|
|
This binding expects a JS string as its 2nd argument and
|
|
null as its final argument. In order to compile multiple
|
|
statements from a single string, the "full" impl (see
|
|
below) must be used.
|
|
*/
|
|
basic: wasm.xWrap('sqlite3_prepare_v3',
|
|
"int", ["sqlite3*", "string",
|
|
"int"/*ignored for this impl!*/,
|
|
"int", "**",
|
|
"**"/*MUST be 0 or null or undefined!*/]),
|
|
/**
|
|
Impl which requires that the 2nd argument be a pointer
|
|
to the SQL string, instead of being converted to a
|
|
string. This variant is necessary for cases where we
|
|
require a non-NULL value for the final argument
|
|
(exec()'ing multiple statements from one input
|
|
string). For simpler cases, where only the first
|
|
statement in the SQL string is required, the wrapper
|
|
named sqlite3_prepare_v2() is sufficient and easier to
|
|
use because it doesn't require dealing with pointers.
|
|
*/
|
|
full: wasm.xWrap('sqlite3_prepare_v3',
|
|
"int", ["sqlite3*", "*", "int", "int",
|
|
"**", "**"])
|
|
};
|
|
|
|
/* Documented in the capi object's initializer. */
|
|
capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){
|
|
if(f.length!==arguments.length){
|
|
return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length);
|
|
}
|
|
const [xSql, xSqlLen] = __flexiString(sql, sqlLen);
|
|
switch(typeof xSql){
|
|
case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null);
|
|
case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail);
|
|
default:
|
|
return util.sqlite3__wasm_db_error(
|
|
pDb, capi.SQLITE_MISUSE,
|
|
"Invalid SQL argument type for sqlite3_prepare_v2/v3()."
|
|
);
|
|
}
|
|
};
|
|
|
|
/* Documented in the capi object's initializer. */
|
|
capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){
|
|
return (f.length===arguments.length)
|
|
? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail)
|
|
: __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length);
|
|
};
|
|
|
|
}/*sqlite3_prepare_v2/v3()*/
|
|
|
|
{/*sqlite3_bind_text/blob()*/
|
|
const __bindText = wasm.xWrap("sqlite3_bind_text", "int", [
|
|
"sqlite3_stmt*", "int", "string", "int", "*"
|
|
]);
|
|
const __bindBlob = wasm.xWrap("sqlite3_bind_blob", "int", [
|
|
"sqlite3_stmt*", "int", "*", "int", "*"
|
|
]);
|
|
|
|
/** Documented in the capi object's initializer. */
|
|
capi.sqlite3_bind_text = function f(pStmt, iCol, text, nText, xDestroy){
|
|
if(f.length!==arguments.length){
|
|
return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt),
|
|
"sqlite3_bind_text", f.length);
|
|
}else if(wasm.isPtr(text) || null===text){
|
|
return __bindText(pStmt, iCol, text, nText, xDestroy);
|
|
}else if(text instanceof ArrayBuffer){
|
|
text = new Uint8Array(text);
|
|
}else if(Array.isArray(pMem)){
|
|
text = pMem.join('');
|
|
}
|
|
let p, n;
|
|
try{
|
|
if(util.isSQLableTypedArray(text)){
|
|
p = wasm.allocFromTypedArray(text);
|
|
n = text.byteLength;
|
|
}else if('string'===typeof text){
|
|
[p, n] = wasm.allocCString(text);
|
|
}else{
|
|
return util.sqlite3__wasm_db_error(
|
|
capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE,
|
|
"Invalid 3rd argument type for sqlite3_bind_text()."
|
|
);
|
|
}
|
|
return __bindText(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC);
|
|
}catch(e){
|
|
wasm.dealloc(p);
|
|
return util.sqlite3__wasm_db_error(
|
|
capi.sqlite3_db_handle(pStmt), e
|
|
);
|
|
}
|
|
}/*sqlite3_bind_text()*/;
|
|
|
|
/** Documented in the capi object's initializer. */
|
|
capi.sqlite3_bind_blob = function f(pStmt, iCol, pMem, nMem, xDestroy){
|
|
if(f.length!==arguments.length){
|
|
return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt),
|
|
"sqlite3_bind_blob", f.length);
|
|
}else if(wasm.isPtr(pMem) || null===pMem){
|
|
return __bindBlob(pStmt, iCol, pMem, nMem, xDestroy);
|
|
}else if(pMem instanceof ArrayBuffer){
|
|
pMem = new Uint8Array(pMem);
|
|
}else if(Array.isArray(pMem)){
|
|
pMem = pMem.join('');
|
|
}
|
|
let p, n;
|
|
try{
|
|
if(util.isBindableTypedArray(pMem)){
|
|
p = wasm.allocFromTypedArray(pMem);
|
|
n = nMem>=0 ? nMem : pMem.byteLength;
|
|
}else if('string'===typeof pMem){
|
|
[p, n] = wasm.allocCString(pMem);
|
|
}else{
|
|
return util.sqlite3__wasm_db_error(
|
|
capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE,
|
|
"Invalid 3rd argument type for sqlite3_bind_blob()."
|
|
);
|
|
}
|
|
return __bindBlob(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC);
|
|
}catch(e){
|
|
wasm.dealloc(p);
|
|
return util.sqlite3__wasm_db_error(
|
|
capi.sqlite3_db_handle(pStmt), e
|
|
);
|
|
}
|
|
}/*sqlite3_bind_blob()*/;
|
|
|
|
}/*sqlite3_bind_text/blob()*/
|
|
|
|
{/* sqlite3_config() */
|
|
/**
|
|
Wraps a small subset of the C API's sqlite3_config() options.
|
|
Unsupported options trigger the return of capi.SQLITE_NOTFOUND.
|
|
Passing fewer than 2 arguments triggers return of
|
|
capi.SQLITE_MISUSE.
|
|
*/
|
|
capi.sqlite3_config = function(op, ...args){
|
|
if(arguments.length<2) return capi.SQLITE_MISUSE;
|
|
switch(op){
|
|
case capi.SQLITE_CONFIG_COVERING_INDEX_SCAN: // 20 /* int */
|
|
case capi.SQLITE_CONFIG_MEMSTATUS:// 9 /* boolean */
|
|
case capi.SQLITE_CONFIG_SMALL_MALLOC: // 27 /* boolean */
|
|
case capi.SQLITE_CONFIG_SORTERREF_SIZE: // 28 /* int nByte */
|
|
case capi.SQLITE_CONFIG_STMTJRNL_SPILL: // 26 /* int nByte */
|
|
case capi.SQLITE_CONFIG_URI:// 17 /* int */
|
|
return wasm.exports.sqlite3__wasm_config_i(op, args[0]);
|
|
case capi.SQLITE_CONFIG_LOOKASIDE: // 13 /* int int */
|
|
return wasm.exports.sqlite3__wasm_config_ii(op, args[0], args[1]);
|
|
case capi.SQLITE_CONFIG_MEMDB_MAXSIZE: // 29 /* sqlite3_int64 */
|
|
return wasm.exports.sqlite3__wasm_config_j(op, args[0]);
|
|
case capi.SQLITE_CONFIG_GETMALLOC: // 5 /* sqlite3_mem_methods* */
|
|
case capi.SQLITE_CONFIG_GETMUTEX: // 11 /* sqlite3_mutex_methods* */
|
|
case capi.SQLITE_CONFIG_GETPCACHE2: // 19 /* sqlite3_pcache_methods2* */
|
|
case capi.SQLITE_CONFIG_GETPCACHE: // 15 /* no-op */
|
|
case capi.SQLITE_CONFIG_HEAP: // 8 /* void*, int nByte, int min */
|
|
case capi.SQLITE_CONFIG_LOG: // 16 /* xFunc, void* */
|
|
case capi.SQLITE_CONFIG_MALLOC:// 4 /* sqlite3_mem_methods* */
|
|
case capi.SQLITE_CONFIG_MMAP_SIZE: // 22 /* sqlite3_int64, sqlite3_int64 */
|
|
case capi.SQLITE_CONFIG_MULTITHREAD: // 2 /* nil */
|
|
case capi.SQLITE_CONFIG_MUTEX: // 10 /* sqlite3_mutex_methods* */
|
|
case capi.SQLITE_CONFIG_PAGECACHE: // 7 /* void*, int sz, int N */
|
|
case capi.SQLITE_CONFIG_PCACHE2: // 18 /* sqlite3_pcache_methods2* */
|
|
case capi.SQLITE_CONFIG_PCACHE: // 14 /* no-op */
|
|
case capi.SQLITE_CONFIG_PCACHE_HDRSZ: // 24 /* int *psz */
|
|
case capi.SQLITE_CONFIG_PMASZ: // 25 /* unsigned int szPma */
|
|
case capi.SQLITE_CONFIG_SERIALIZED: // 3 /* nil */
|
|
case capi.SQLITE_CONFIG_SINGLETHREAD: // 1 /* nil */:
|
|
case capi.SQLITE_CONFIG_SQLLOG: // 21 /* xSqllog, void* */
|
|
case capi.SQLITE_CONFIG_WIN32_HEAPSIZE: // 23 /* int nByte */
|
|
default:
|
|
/* maintenance note: we specifically do not include
|
|
SQLITE_CONFIG_ROWID_IN_VIEW here, on the grounds that
|
|
it's only for legacy support and no apps written with
|
|
this API require that. */
|
|
return capi.SQLITE_NOTFOUND;
|
|
}
|
|
};
|
|
}/* sqlite3_config() */
|
|
|
|
{/*auto-extension bindings.*/
|
|
const __autoExtFptr = new Set;
|
|
|
|
capi.sqlite3_auto_extension = function(fPtr){
|
|
if( fPtr instanceof Function ){
|
|
fPtr = wasm.installFunction('i(ppp)', fPtr);
|
|
}else if( 1!==arguments.length || !wasm.isPtr(fPtr) ){
|
|
return capi.SQLITE_MISUSE;
|
|
}
|
|
const rc = wasm.exports.sqlite3_auto_extension(fPtr);
|
|
if( fPtr!==arguments[0] ){
|
|
if(0===rc) __autoExtFptr.add(fPtr);
|
|
else wasm.uninstallFunction(fPtr);
|
|
}
|
|
return rc;
|
|
};
|
|
|
|
capi.sqlite3_cancel_auto_extension = function(fPtr){
|
|
/* We do not do an automatic JS-to-WASM function conversion here
|
|
because it would be senseless: the converted pointer would
|
|
never possibly match an already-installed one. */;
|
|
if(!fPtr || 1!==arguments.length || !wasm.isPtr(fPtr)) return 0;
|
|
return wasm.exports.sqlite3_cancel_auto_extension(fPtr);
|
|
/* Note that it "cannot happen" that a client passes a pointer which
|
|
is in __autoExtFptr because __autoExtFptr only contains automatic
|
|
conversions created inside sqlite3_auto_extension() and
|
|
never exposed to the client. */
|
|
};
|
|
|
|
capi.sqlite3_reset_auto_extension = function(){
|
|
wasm.exports.sqlite3_reset_auto_extension();
|
|
for(const fp of __autoExtFptr) wasm.uninstallFunction(fp);
|
|
__autoExtFptr.clear();
|
|
};
|
|
}/* auto-extension */
|
|
|
|
const pKvvfs = capi.sqlite3_vfs_find("kvvfs");
|
|
if( pKvvfs ){/* kvvfs-specific glue */
|
|
if(util.isUIThread()){
|
|
const kvvfsMethods = new capi.sqlite3_kvvfs_methods(
|
|
wasm.exports.sqlite3__wasm_kvvfs_methods()
|
|
);
|
|
delete capi.sqlite3_kvvfs_methods;
|
|
|
|
const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack,
|
|
pstack = wasm.pstack;
|
|
|
|
const kvvfsStorage = (zClass)=>
|
|
((115/*=='s'*/===wasm.peek(zClass))
|
|
? sessionStorage : localStorage);
|
|
|
|
/**
|
|
Implementations for members of the object referred to by
|
|
sqlite3__wasm_kvvfs_methods(). We swap out the native
|
|
implementations with these, which use localStorage or
|
|
sessionStorage for their backing store.
|
|
*/
|
|
const kvvfsImpls = {
|
|
xRead: (zClass, zKey, zBuf, nBuf)=>{
|
|
const stack = pstack.pointer,
|
|
astack = wasm.scopedAllocPush();
|
|
try {
|
|
const zXKey = kvvfsMakeKey(zClass,zKey);
|
|
if(!zXKey) return -3/*OOM*/;
|
|
const jKey = wasm.cstrToJs(zXKey);
|
|
const jV = kvvfsStorage(zClass).getItem(jKey);
|
|
if(!jV) return -1;
|
|
const nV = jV.length /* Note that we are relying 100% on v being
|
|
ASCII so that jV.length is equal to the
|
|
C-string's byte length. */;
|
|
if(nBuf<=0) return nV;
|
|
else if(1===nBuf){
|
|
wasm.poke(zBuf, 0);
|
|
return nV;
|
|
}
|
|
const zV = wasm.scopedAllocCString(jV);
|
|
if(nBuf > nV + 1) nBuf = nV + 1;
|
|
wasm.heap8u().copyWithin(zBuf, zV, zV + nBuf - 1);
|
|
wasm.poke(zBuf + nBuf - 1, 0);
|
|
return nBuf - 1;
|
|
}catch(e){
|
|
console.error("kvstorageRead()",e);
|
|
return -2;
|
|
}finally{
|
|
pstack.restore(stack);
|
|
wasm.scopedAllocPop(astack);
|
|
}
|
|
},
|
|
xWrite: (zClass, zKey, zData)=>{
|
|
const stack = pstack.pointer;
|
|
try {
|
|
const zXKey = kvvfsMakeKey(zClass,zKey);
|
|
if(!zXKey) return 1/*OOM*/;
|
|
const jKey = wasm.cstrToJs(zXKey);
|
|
kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData));
|
|
return 0;
|
|
}catch(e){
|
|
console.error("kvstorageWrite()",e);
|
|
return capi.SQLITE_IOERR;
|
|
}finally{
|
|
pstack.restore(stack);
|
|
}
|
|
},
|
|
xDelete: (zClass, zKey)=>{
|
|
const stack = pstack.pointer;
|
|
try {
|
|
const zXKey = kvvfsMakeKey(zClass,zKey);
|
|
if(!zXKey) return 1/*OOM*/;
|
|
kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey));
|
|
return 0;
|
|
}catch(e){
|
|
console.error("kvstorageDelete()",e);
|
|
return capi.SQLITE_IOERR;
|
|
}finally{
|
|
pstack.restore(stack);
|
|
}
|
|
}
|
|
}/*kvvfsImpls*/;
|
|
for(const k of Object.keys(kvvfsImpls)){
|
|
kvvfsMethods[kvvfsMethods.memberKey(k)] =
|
|
wasm.installFunction(
|
|
kvvfsMethods.memberSignature(k),
|
|
kvvfsImpls[k]
|
|
);
|
|
}
|
|
}else{
|
|
/* Worker thread: unregister kvvfs to avoid it being used
|
|
for anything other than local/sessionStorage. It "can"
|
|
be used that way but it's not really intended to be. */
|
|
capi.sqlite3_vfs_unregister(pKvvfs);
|
|
}
|
|
}/*pKvvfs*/
|
|
|
|
/* Warn if client-level code makes use of FuncPtrAdapter. */
|
|
wasm.xWrap.FuncPtrAdapter.warnOnUse = true;
|
|
|
|
const StructBinder = sqlite3.StructBinder
|
|
/* we require a local alias b/c StructBinder is removed from the sqlite3
|
|
object during the final steps of the API cleanup. */;
|
|
/**
|
|
Installs a StructBinder-bound function pointer member of the
|
|
given name and function in the given StructBinder.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 may leave the C
|
|
environment in an undefined state.
|
|
*/
|
|
const installMethod = function callee(
|
|
tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck
|
|
){
|
|
if(!(tgt instanceof 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
|
|
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 StructBinder.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).
|
|
*/
|
|
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.
|
|
*/
|
|
StructBinder.StructType.prototype.installMethods = function(
|
|
methods, applyArgcCheck = installMethod.installMethodArgcCheck
|
|
){
|
|
return installMethods(this, methods, applyArgcCheck);
|
|
};
|
|
|
|
});
|