diff options
author | stephan <stephan@noemail.net> | 2022-09-30 20:35:37 +0000 |
---|---|---|
committer | stephan <stephan@noemail.net> | 2022-09-30 20:35:37 +0000 |
commit | e67a0f40e419f4945d8157cb047ae302bf1f2852 (patch) | |
tree | a31e7863c9e6867e74ce53be5545eef1e5e02c9a /ext/wasm/api | |
parent | d18f1bbfe06e40713de908cd894d9857f6c11606 (diff) | |
download | sqlite-e67a0f40e419f4945d8157cb047ae302bf1f2852.tar.gz sqlite-e67a0f40e419f4945d8157cb047ae302bf1f2852.zip |
Add JS wrapper for sqlite3_exec() which knows how to handle a JS callback. Add some console.error() reporting of module-load failures, as they otherwise often get silently swallowed up by the loader's mechanisms. Add 'flexible-string' JS-to-WASM argument converter which performs more X-to-string conversions than the 'string' arg converter does.
FossilOrigin-Name: 96818aa83f4ccc574f558231249ecbdd39763b4351cf4cf6d33f53774a3ee5e6
Diffstat (limited to 'ext/wasm/api')
-rw-r--r-- | ext/wasm/api/extern-post-js.js | 5 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-cleanup.js | 3 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-glue.js | 186 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-api-prologue.js | 50 |
4 files changed, 188 insertions, 56 deletions
diff --git a/ext/wasm/api/extern-post-js.js b/ext/wasm/api/extern-post-js.js index 25d2d1b32..5ebb5a31e 100644 --- a/ext/wasm/api/extern-post-js.js +++ b/ext/wasm/api/extern-post-js.js @@ -15,7 +15,6 @@ if(!originalInit){ throw new Error("Expecting self.sqlite3InitModule to be defined by the Emscripten build."); } - self.sqlite3InitModule.ready = originalInit.ready; self.sqlite3InitModule = (...args)=>{ //console.warn("Using replaced sqlite3InitModule()",self.location); return originalInit(...args).then((EmscriptenModule)=>{ @@ -35,7 +34,11 @@ const f = EmscriptenModule.sqlite3.asyncPostInit; delete EmscriptenModule.sqlite3.asyncPostInit; return f(); + }).catch((e)=>{ + console.error("Exception loading sqlite3 module:",e); + throw e; }); }; + self.sqlite3InitModule.ready = originalInit.ready; //console.warn("Replaced sqlite3InitModule()"); })(); diff --git a/ext/wasm/api/sqlite3-api-cleanup.js b/ext/wasm/api/sqlite3-api-cleanup.js index 12d766886..c2a342589 100644 --- a/ext/wasm/api/sqlite3-api-cleanup.js +++ b/ext/wasm/api/sqlite3-api-cleanup.js @@ -46,6 +46,9 @@ if('undefined' !== typeof Module){ // presumably an Emscripten build let sqlite3; try{ sqlite3 = self.sqlite3ApiBootstrap(); + }catch(e){ + console.error("sqlite3ApiBootstrap() error:",e); + throw e; }finally{ delete self.sqlite3ApiBootstrap; if(rmApiConfig) delete self.sqlite3ApiConfig; diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 2082f427b..347c03bc4 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -33,20 +33,27 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ to a C-style sqlite3_xxx function which takes an `sqlite3*` argument. */ - const oldP = wasm.xWrap.argAdapter('pointer'); + const xPointer = wasm.xWrap.argAdapter('pointer'); const adapter = function(v){ - if(v && 'object'===typeof v && v.constructor){ + if(v && v.constructor){ const x = v.pointer; if(Number.isInteger(x)) return x; - else toss("Invalid (object) type for pointer-type argument."); + else toss("Invalid (object) type for .pointer-type argument."); } - return oldP(v); + return xPointer(v); }; wasm.xWrap.argAdapter('.pointer', adapter); } /* ".pointer" xWrap() argument adapter */ - // WhWasmUtil.xWrap() bindings... - { + if(1){/* Convert Arrays and certain TypedArrays to strings for + 'flexible-string'-type arguments */ + const xString = wasm.xWrap.argAdapter('string'); + wasm.xWrap.argAdapter( + 'flexible-string', (v)=>xString(util.arrayToString(v)) + ); + } + + if(1){// WhWasmUtil.xWrap() bindings... /** Add some descriptive xWrap() aliases for '*' intended to (A) initially improve readability/correctness of capi.signatures @@ -107,51 +114,139 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/*xWrap() bindings*/; /** - Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). + 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 __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!*/]); + const __dbArgcMismatch = (pDb,f,n)=>{ + return sqlite3.util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE, + f+"() requires "+n+" argument"+ + (1===n?'':'s')+"."); + }; + /** - 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. + Helper for flexible-string 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). */ - __prepare.full = wasm.xWrap('sqlite3_prepare_v3', - "int", ["sqlite3*", "*", "int", "int", - "**", "**"]); - - /* Documented in the api object's initializer. */ - capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ - if(util.isSQLableTypedArray(sql)) sql = util.typedArrayToString(sql); - switch(typeof sql){ - case 'string': return __prepare.basic(pDb, sql, -1, prepFlags, ppStmt, null); - case 'number': return __prepare.full(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail); - default: - return util.sqlite3_wasm_db_error( - pDb, capi.SQLITE_MISUSE, - "Invalid SQL argument type for sqlite3_prepare_v2/v3()." - ); + const __flexiString = function(v,n){ + if('string'===typeof v){ + n = -1; + }else if(util.isSQLableTypedArray(v)){ + n = v.byteLength; + v = util.typedArrayToString(v); + }else if(Array.isArray(v)){ + v = v.join(''); + n = -1; } + return [v, n]; }; - capi.sqlite3_prepare_v2 = - (pDb, sql, sqlLen, ppStmt, pzTail)=>capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail); + if(1){/* Special-case handling of sqlite3_exec() */ + const __exec = wasm.xWrap("sqlite3_exec", "int", + ["sqlite3*", "flexible-string", "*", "*", "**"]); + /* Documented in the api object's initializer. */ + capi.sqlite3_exec = function(pDb, sql, callback, pVoid, pErrMsg){ + if(5!==arguments.length){ + return __dbArgcMismatch(pDb,"sqlite3_exec",5); + }else if('function' !== typeof callback){ + return __exec(pDb, sql, callback, pVoid, pErrMsg); + } + /* Wrap the callback in a WASM-bound function and convert the callback's + `(char**)` arguments to arrays of strings... */ + const wasm = capi.wasm; + const cbwrap = function(pVoid, nCols, pColVals, pColNames){ + let rc = capi.SQLITE_ERROR; + try { + let aVals = [], aNames = [], i = 0, offset = 0; + for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){ + aVals.push( wasm.cstringToJs(wasm.getPtrValue(pColVals + offset)) ); + aNames.push( wasm.cstringToJs(wasm.getPtrValue(pColNames + offset)) ); + } + rc = callback(pVoid, nCols, aVals, aNames) | 0; + /* The first 2 args of the callback are useless for JS but + we want the JS mapping of the C API to be as close to the + C API as possible. */ + }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. */ + } + return rc; + }; + let pFunc, rc; + try{ + pFunc = wasm.installFunction("ipipp", cbwrap); + rc = __exec(pDb, sql, pFunc, 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); + } + return rc; + }; + }/*sqlite3_exec() proxy*/; + + if(1){/* Special-case handling of sqlite3_prepare_v2() and + sqlite3_prepare_v3() */ + /** + 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", + "**", "**"]); + + /* Documented in the api object's initializer. */ + capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ + if(6!==arguments.length){ + return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",6); + } + 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 api object's initializer. */ + capi.sqlite3_prepare_v2 = function(pDb, sql, sqlLen, ppStmt, pzTail){ + return (5==arguments.length) + ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) + : __dbArgcMismatch(pDb,"sqlite3_prepare_v2",5); + }; + }/*sqlite3_prepare_v2/v3()*/; /** Install JS<->C struct bindings for the non-opaque struct types we @@ -208,3 +303,4 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ sourceId: sqlite3.capi.sqlite3_sourceid() }); }); + diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index 760c4b0b4..b738485cb 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -244,6 +244,17 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }; /** + If v is-a Array, its join('') result is returned. If + isSQLableTypedArray(v) then typedArrayToString(v) is + returned. Else v is returned as-is. + */ + const arrayToString = function(v){ + if(isSQLableTypedArray(v)) return typedArrayToString(v); + else if(Array.isArray(v)) return v.join(''); + return v; + }; + + /** An Error subclass specifically for reporting Wasm-level malloc() failure and enabling clients to unambiguously identify such exceptions. @@ -350,16 +361,35 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( comments or an empty SQL expression, 0 is returned but the result output pointer will be NULL. */ - sqlite3_prepare_v3: function(dbPtr, sql, sqlByteLen, prepFlags, - stmtPtrPtr, strPtrPtr){}/*installed later*/, + sqlite3_prepare_v3: (dbPtr, sql, sqlByteLen, prepFlags, + stmtPtrPtr, strPtrPtr)=>{}/*installed later*/, /** Equivalent to calling sqlite3_prapare_v3() with 0 as its 4th argument. */ - sqlite3_prepare_v2: function(dbPtr, sql, sqlByteLen, stmtPtrPtr, - strPtrPtr){}/*installed later*/, + sqlite3_prepare_v2: (dbPtr, sql, sqlByteLen, + stmtPtrPtr,strPtrPtr)=>{}/*installed later*/, /** + This binding enables the callback argument to be a JavaScript. + + If the callback is a function, then for the duration of the + sqlite3_exec() call, it installs a WASM-bound function which + acts as a proxy for the given callback. That proxy will + also perform a conversion of the callback's arguments from + `(char**)` to JS arrays of strings. However, for API + consistency's sake it will still honor the C-level + callback parameter order and will call it like: + + `callback(pVoid, colCount, listOfValues, listOfColNames)` + + If the callback is not a JS function then this binding performs + no translation of the callback, but the sql argument is still + converted to a WASM string for the call using the + "flexible-string" argument converter. + */ + sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/, + /** Various internal-use utilities are added here as needed. They are bound to an object only so that we have access to them in the differently-scoped steps of the API bootstrapping @@ -367,8 +397,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( removed. */ util:{ - isInt32, isTypedArray, isBindableTypedArray, isSQLableTypedArray, - affirmBindableTypedArray, typedArrayToString, + affirmBindableTypedArray, arrayToString, isBindableTypedArray, + isInt32, isSQLableTypedArray, isTypedArray, + typedArrayToString, isMainWindow: ()=>{ return self.window===self && self.document; } @@ -614,10 +645,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( ["sqlite3_errmsg", "string", "sqlite3*"], ["sqlite3_error_offset", "int", "sqlite3*"], ["sqlite3_errstr", "string", "int"], - ["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**"], - // TODO?: ^^^ add a wrapper around sqlite3_exec() which accepts a - // JS callback function and handles the (un)installation of that - // function before/after the exec call. + /*["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"], |