1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Move JS-to-C binding signatures from sqlite3-api-prologue.js to sqlite3-api-glue.js to allow for use of the new/experimental sqlite3.wasm.xWrap() feature which automatically binds JS functions to WASM/C as needed, which simplifies creation of bindings which take C function pointers. Reimplement sqlite3_exec(), sqlite3_create_collation(), sqlite3_progress_handler() to use this new feature.

FossilOrigin-Name: 9386d6f634680b4e0fa5487c34c63acb29f0b7a6ae738b8f6164ad084a229b62
This commit is contained in:
stephan
2022-12-12 14:31:38 +00:00
parent 5dbfc0dfdd
commit 124fc52d96
6 changed files with 475 additions and 331 deletions

View File

@ -24,6 +24,251 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
self.WhWasmUtilInstaller(wasm);
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)',
bindMode: 'singleton'
}), "*"
]
],
["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", "*", "*"],
["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
need... */
@ -166,7 +411,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Sets the given db's error state. Accepts:
- (sqlite3*, int code, string msg)
- (sqlite3*, Error [,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,
@ -183,7 +428,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
resultCode = capi.SQLITE_NOMEM;
message = 0 /*avoid allocating message string*/;
}else if(resultCode instanceof Error){
message = message || resultCode.message;
message = message || ''+resultCode;
resultCode = (resultCode.resultCode || capi.SQLITE_ERROR);
}
return __db_err(pDb, resultCode, message);
@ -211,15 +456,29 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const __ccv2 = wasm.xWrap(
'sqlite3_create_collation_v2', 'int',
'sqlite3*','string','int','*','*','*'
/* int(*xCompare)(void*,int,const void*,int,const void*) */
/* void(*xDestroy(void*) */);
'sqlite3*','string','int','*',
new wasm.xWrap.FuncPtrAdapter({
/* int(*xCompare)(void*,int,const void*,int,const void*) */
name: 'xCompare',
signature: 'i(pipip)',
bindMode: 'static'
}),
new wasm.xWrap.FuncPtrAdapter({
/* void(*xDestroy(void*) */
name: 'xDestroy',
signature: 'v(p)',
bindMode: 'static'
})
);
/**
Works exactly like C's sqlite3_create_collation_v2() except that:
1) It permits its two function arguments to be JS functions,
for which it will install WASM-bound proxies.
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.
@ -235,19 +494,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding."
);
}
let rc, pfCompare, pfDestroy;
let rc;
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);
rc = __ccv2(pDb, zName, eTextRep, pArg, xCompare, xDestroy);
}catch(e){
if(pfCompare) wasm.uninstallFunction(pfCompare);
if(pfDestroy) wasm.uninstallFunction(pfDestroy);
rc = util.sqlite3_wasm_db_error(pDb, e);
}
return rc;
@ -259,17 +509,20 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
: __dbArgcMismatch(pDb, 'sqlite3_create_collation', 5);
}
}/*sqlite3_create_collation() and friends*/
if(1){/* Special-case handling of sqlite3_exec() */
const __exec = wasm.xWrap("sqlite3_exec", "int",
["sqlite3*", "string:flexible", "*", "*", "**"]);
["sqlite3*", "string:flexible",
new wasm.xWrap.FuncPtrAdapter({
signature: 'i(pipp)',
bindMode: 'transient'
}), "*", "**"]);
/* Documented in the api object's initializer. */
capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){
if(f.length!==arguments.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);
}
/* Wrap the callback in a WASM-bound function and convert the callback's
@ -294,15 +547,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
return rc;
};
let pFunc, rc;
let rc;
try{
pFunc = wasm.installFunction("ipipp", cbwrap);
rc = __exec(pDb, sql, pFunc, pVoid, pErrMsg);
rc = __exec(pDb, sql, cbwrap, pVoid, pErrMsg);
}catch(e){
rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR,
"Error running exec(): "+e.message);
}finally{
if(pFunc) wasm.uninstallFunction(pFunc);
"Error running exec(): "+e);
}
return rc;
};