Reimplement JS's sqlite3_bind_text/blob() with hand-written bindings to permit more flexible inputs. Add automated JS-to-C function conversion to sqlite3_busy_handler(). sqlite3.wasm.xWrap()'s '*' argument conversion no longer treats JS strings as C-strings: those conversions require explicit opt-in via the 'string' converter (or equivalent).

FossilOrigin-Name: 96ba44946b3e88b6aa305c4363cbbfeab0d9120b3d8c4d2587d68b9293ea7cc6
This commit is contained in:
stephan 2022-12-23 23:46:33 +00:00
parent ab9c2d571e
commit 027afccdcd
6 changed files with 240 additions and 88 deletions

View File

@ -42,10 +42,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
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_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"],
@ -53,16 +51,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
["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", "*"
/* 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_handler","int", [
"sqlite3*",
new wasm.xWrap.FuncPtrAdapter({
signature: 'i(pi)',
contextKey: (argIndex,argv)=>'sqlite3@'+argv[0]
}),
"*"
]],
["sqlite3_busy_timeout","int", "sqlite3*", "int"],
["sqlite3_close_v2", "int", "sqlite3*"],
["sqlite3_changes", "int", "sqlite3*"],
@ -779,6 +775,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(1){/* 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
@ -802,32 +799,33 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/**
Scope-local holder of the two impls of sqlite3_prepare_v2/v3().
*/
const __prepare = Object.create(null);
/**
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.
*/
__prepare.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.
*/
__prepare.full = wasm.xWrap('sqlite3_prepare_v3',
"int", ["sqlite3*", "*", "int", "int",
"**", "**"]);
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){
@ -852,7 +850,80 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail)
: __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length);
};
}/*sqlite3_prepare_v2/v3()*/;
}/*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_TRANSIENT);
}finally{
wasm.dealloc(p);
}
}/*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_TRANSIENT);
}finally{
wasm.dealloc(p);
}
}/*sqlite3_bind_blob()*/;
}/*sqlite3_bind_text/blob()*/
{/* sqlite3_set_authorizer() */
const __ssa = wasm.xWrap("sqlite3_set_authorizer", 'int', [

View File

@ -1332,8 +1332,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}else if(!util.isBindableTypedArray(val)){
toss3("Binding a value as a blob requires",
"that it be a string, Uint8Array, or Int8Array.");
}else if(1){
/* _Hypothetically_ more efficient than the impl in the 'else' block. */
}else{
const stack = wasm.scopedAllocPush();
try{
const pBlob = wasm.scopedAlloc(val.byteLength || 1);
@ -1343,14 +1342,6 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}finally{
wasm.scopedAllocPop(stack);
}
}else{
const pBlob = wasm.allocFromTypedArray(val);
try{
rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength,
capi.SQLITE_TRANSIENT);
}finally{
wasm.dealloc(pBlob);
}
}
break;
}

View File

@ -417,6 +417,92 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
};
Object.assign(capi, {
/**
sqlite3_bind_blob() works exactly like its C counterpart unless
its 3rd argument is one of:
- JS string: the 3rd argument is converted to a C string, the
4th argument is ignored, and the C-string's length is used
in its place.
- Array: converted to a string as defined for "flexible
strings" and then it's treated as a JS string.
- Int8Array or Uint8Array: wasm.allocFromTypedArray() is used to
conver the memory to the WASM heap. If the 4th argument is
0 or greater, it is used as-is, otherwise the array's byteLength
value is used. This is an exception to the C API's undefined
behavior for a negative 4th argument, but results are undefined
if the given 4th argument value is greater than the byteLength
of the input array.
- If it's an ArrayBuffer, it gets wrapped in a Uint8Array and
treated as that type.
In all of those cases, the final argument (text destructor) is
ignored and capi.SQLITE_TRANSIENT is assumed.
A 3rd argument of `null` is treated as if it were a WASM pointer
of 0.
If the 3rd argument is neither a WASM pointer nor one of the
above-described types, capi.SQLITE_MISUSE is returned.
The first argument may be either an `sqlite3_stmt*` WASM
pointer or an sqlite3.oo1.Stmt instance.
For consistency with the C API, it requires the same number of
arguments. It returns capi.SQLITE_MISUSE if passed any other
argument count.
*/
sqlite3_bind_blob: undefined/*installed later*/,
/**
sqlite3_bind_text() works exactly like its C counterpart unless
its 3rd argument is one of:
- JS string: the 3rd argument is converted to a C string, the
4th argument is ignored, and the C-string's length is used
in its place.
- Array: converted to a string as defined for "flexible
strings". The 4th argument is ignored and a value of -1
is assumed.
- Int8Array or Uint8Array: is assumed to contain UTF-8 text, is
converted to a string. The 4th argument is ignored, replaced
by the array's byteLength value.
- If it's an ArrayBuffer, it gets wrapped in a Uint8Array and
treated as that type.
In each of those cases, the final argument (text destructor) is
ignored and capi.SQLITE_TRANSIENT is assumed.
A 3rd argument of `null` is treated as if it were a WASM pointer
of 0.
If the 3rd argument is neither a WASM pointer nor one of the
above-described types, capi.SQLITE_MISUSE is returned.
The first argument may be either an `sqlite3_stmt*` WASM
pointer or an sqlite3.oo1.Stmt instance.
For consistency with the C API, it requires the same number of
arguments. It returns capi.SQLITE_MISUSE if passed any other
argument count.
If client code needs to bind partial strings, it needs to
either parcel the string up before passing it in here or it
must pass in a WASM pointer for the 3rd argument and a valid
4th-argument value, taking care not to pass a value which
truncates a multi-byte UTF-8 character. When passing
WASM-format strings, it is important that the final argument be
valid or unexpected content can result can result, or even a
crash if the application reads past the WASM heap bounds.
*/
sqlite3_bind_text: undefined/*installed later*/,
/**
sqlite3_create_function_v2() differs from its native
counterpart only in the following ways:
@ -525,18 +611,18 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH`
flag.
*/
sqlite3_create_function_v2: function(
sqlite3_create_function_v2: (
pDb, funcName, nArg, eTextRep, pApp,
xFunc, xStep, xFinal, xDestroy
){/*installed later*/},
)=>{/*installed later*/},
/**
Equivalent to passing the same arguments to
sqlite3_create_function_v2(), with 0 as the final argument.
*/
sqlite3_create_function:function(
sqlite3_create_function: (
pDb, funcName, nArg, eTextRep, pApp,
xFunc, xStep, xFinal
){/*installed later*/},
)=>{/*installed later*/},
/**
The sqlite3_create_window_function() JS wrapper differs from
its native implementation in the exact same way that
@ -544,10 +630,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
xInverse(), is treated identically to xStep() by the wrapping
layer.
*/
sqlite3_create_window_function: function(
sqlite3_create_window_function: (
pDb, funcName, nArg, eTextRep, pApp,
xStep, xFinal, xValue, xInverse, xDestroy
){/*installed later*/},
)=>{/*installed later*/},
/**
The sqlite3_prepare_v3() binding handles two different uses
with differing JS/WASM semantics:
@ -669,7 +755,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
toss3,
typedArrayPart
};
Object.assign(wasm, {
/**
Emscripten APIs have a deep-seated assumption that all pointers

View File

@ -1406,7 +1406,8 @@ self.WhWasmUtilInstaller = function(target){
.set('int', xArg.get('i32'))
.set('null', (i)=>i)
.set(null, xArg.get('null'))
.set('**', __xArgPtr);
.set('**', __xArgPtr)
.set('*', __xArgPtr);
xResult.set('*', __xArgPtr)
.set('pointer', __xArgPtr)
.set('number', (v)=>Number(v))
@ -1448,23 +1449,23 @@ self.WhWasmUtilInstaller = function(target){
if('string'===typeof v) return target.scopedAllocCString(v);
return v ? __xArgPtr(v) : null;
};
xArg.set('string', __xArgString);
xArg.set('utf8', __xArgString);
xArg.set('pointer', __xArgString);
xArg.set('*', __xArgString);
xArg.set('string', __xArgString)
.set('utf8', __xArgString)
.set('pointer', __xArgString);
//xArg.set('*', __xArgString);
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) }
});
xResult.set('utf8:dealloc', xResult.get('string:dealloc'));
xResult.set('json', (i)=>JSON.parse(target.cstrToJs(i)));
xResult.set('json:dealloc', (i)=>{
try{ return i ? JSON.parse(target.cstrToJs(i)) : null }
finally{ target.dealloc(i) }
});
xResult.set('string', (i)=>target.cstrToJs(i))
.set('utf8', xResult.get('string'))
.set('string:dealloc', (i)=>{
try { return i ? target.cstrToJs(i) : null }
finally{ target.dealloc(i) }
})
.set('utf8:dealloc', xResult.get('string:dealloc'))
.set('json', (i)=>JSON.parse(target.cstrToJs(i)))
.set('json:dealloc', (i)=>{
try{ return i ? JSON.parse(target.cstrToJs(i)) : null }
finally{ target.dealloc(i) }
});
/**
Internal-use-only base class for FuncPtrAdapter and potentially
@ -1748,10 +1749,13 @@ self.WhWasmUtilInstaller = function(target){
- `N*` (args): a type name in the form `N*`, where N is a numeric
type name, is treated the same as WASM pointer.
- `*` and `pointer` (args): have multple semantics. They
behave exactly as described below for `string` args.
- `*` and `pointer` (args): are assumed to be WASM pointer values
and are returned coerced to an appropriately-sized pointer
value (i32 or i64). Non-numeric values will coerce to 0 and
out-of-range values will have undefined results (just as with
any pointer misuse).
- `*` and `pointer` (results): are aliases for the current
- `*` and `pointer` (results): aliases for the current
WASM pointer numeric type.
- `**` (args): is simply a descriptive alias for the WASM pointer

View File

@ -1,5 +1,5 @@
C Internal\sJS\scleanups.\sCorrect\spart\sof\s[ac136925a645]\sto\saccount\sfor\sthe\seTextRep\sflag\sbeing\sable\sto\shold\sflags\sother\sthan\sthe\sencoding.
D 2022-12-23T21:10:49.493
C Reimplement\sJS's\ssqlite3_bind_text/blob()\swith\shand-written\sbindings\sto\spermit\smore\sflexible\sinputs.\sAdd\sautomated\sJS-to-C\sfunction\sconversion\sto\ssqlite3_busy_handler().\ssqlite3.wasm.xWrap()'s\s'*'\sargument\sconversion\sno\slonger\streats\sJS\sstrings\sas\sC-strings:\sthose\sconversions\srequire\sexplicit\sopt-in\svia\sthe\s'string'\sconverter\s(or\sequivalent).
D 2022-12-23T23:46:33.608
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -503,9 +503,9 @@ F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08
F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62
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-glue.js f50211dc11f1debf972cdaea87cb4df9772022828072e7100cf5025d1dca2a78
F ext/wasm/api/sqlite3-api-oo1.js 4cce9671e8a31ac9b76bd8559e7827ccc2b121734e460fa9c7d243735a771ec8
F ext/wasm/api/sqlite3-api-prologue.js 789639d256e3134563c5c9be25ade28b40e06e783885d26cccc01cd1ed4b820d
F ext/wasm/api/sqlite3-api-glue.js f0651048a2601bf79f7f39c2c855f6417e65548417f5019ac9ac2ffb2463f2b9
F ext/wasm/api/sqlite3-api-oo1.js c2a3e310f993a632b6c5da0c49b0635863a73df60c4f9fc0a30648f25e4ec32a
F ext/wasm/api/sqlite3-api-prologue.js 683956ea6ab5e0132db48bb693a6bb9dd92f36c8c0902af36572e9b29006ac6d
F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js 7795b84b66a7a8dedc791340709b310bb497c3c72a80bef364fa2a58e2ddae3f
@ -521,7 +521,7 @@ F ext/wasm/c-pp.c 92285f7bce67ed7b7020b40fde8ed0982c442b63dc33df9dfd4b658d4a6c07
F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b
F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15
F ext/wasm/common/testing.css 0ff15602a3ab2bad8aef2c3bd120c7ee3fd1c2054ad2ace7e214187ae68d926f
F ext/wasm/common/whwasmutil.js 97807770ec452fdcaa48509c5decd3a2c1e3001164a62a3223a347f66967533e
F ext/wasm/common/whwasmutil.js 700fb1b702986522d2177fe8247bfbab3040e82cb4f6c35e929c4c85fbd7ffc5
F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed
F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508
F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6
@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P deffe6fb211410fa1a1fbca824a52b4e09b54d4b4f4a4e12d71c9e4b7e8606fb
R 537077e8e7a576993086a957d5c52d05
P 1dfc03ab1e0269807beef27bf884ab9ead7553d4a5f6ed213f812d7fa052045f
R 0557f08bf576b7be78ec588d2742348d
U stephan
Z 379d99233453fc912eb71dcbaf16b8ce
Z 4e12d5aedea06f895991428eb871a6d7
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
1dfc03ab1e0269807beef27bf884ab9ead7553d4a5f6ed213f812d7fa052045f
96ba44946b3e88b6aa305c4363cbbfeab0d9120b3d8c4d2587d68b9293ea7cc6