From 027afccdcd6e9077b7043545a3f8bd32cfb5a397 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 23 Dec 2022 23:46:33 +0000 Subject: 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 --- ext/wasm/api/sqlite3-api-glue.js | 153 ++++++++++++++++++++++++++++----------- 1 file changed, 112 insertions(+), 41 deletions(-) (limited to 'ext/wasm/api/sqlite3-api-glue.js') diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index a760e921f..dd80963f3 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -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', [ -- cgit v1.2.3 From 4099b3cab3c3451a2d9643738308be0b2d9e44b1 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 24 Dec 2022 15:28:45 +0000 Subject: Replace JS-side use of SQLITE_TRANSIENT with the new SQLITE_WASM_DEALLOC, reducing the amount allocation/copying required by sqlite3_bind_blob/text() and sqlite3_result_blob/text(). Remove the 'experimental' log message from the virtual table tests. FossilOrigin-Name: ffe2999a91a7dec129a38afb675fe9e539d7c347886bfea85cba55f6367d54d1 --- ext/wasm/api/sqlite3-api-glue.js | 43 +++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) (limited to 'ext/wasm/api/sqlite3-api-glue.js') diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index dd80963f3..7b5fa1181 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -24,6 +24,33 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ self.WhWasmUtilInstaller(wasm); delete self.WhWasmUtilInstaller; + { + /** + 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: @@ -874,7 +901,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ text = pMem.join(''); } let p, n; - try { + try{ if(util.isSQLableTypedArray(text)){ p = wasm.allocFromTypedArray(text); n = text.byteLength; @@ -886,9 +913,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "Invalid 3rd argument type for sqlite3_bind_text()." ); } - return __bindText(pStmt, iCol, p, n, capi.SQLITE_TRANSIENT); - }finally{ + 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()*/; @@ -917,9 +947,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "Invalid 3rd argument type for sqlite3_bind_blob()." ); } - return __bindBlob(pStmt, iCol, p, n, capi.SQLITE_TRANSIENT); - }finally{ + 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()*/; -- cgit v1.2.3 From 75c04ba89cee1bb6b7b2b599eeacfbd063c84703 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 25 Dec 2022 10:22:27 +0000 Subject: Simplify the signature for JS functions, as opposed to function pointers, passed to sqlite3_exec(), eliminating the superfluous initial two arguments. Update related tests to demonstrate both function-passing approaches. FossilOrigin-Name: e7cc70cdda426863f82ebe1305f4c3053824c5a605b1516b0b7f205f1203178b --- ext/wasm/api/sqlite3-api-glue.js | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) (limited to 'ext/wasm/api/sqlite3-api-glue.js') diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 7b5fa1181..0ef796664 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -234,7 +234,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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 @@ -569,7 +569,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/*sqlite3_create_collation() and friends*/ - if(1){/* Special-case handling of sqlite3_exec() */ + {/* Special-case handling of sqlite3_exec() */ const __exec = wasm.xWrap("sqlite3_exec", "int", ["sqlite3*", "string:flexible", new wasm.xWrap.FuncPtrAdapter({ @@ -585,23 +585,23 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } /* Wrap the callback in a WASM-bound function and convert the callback's `(char**)` arguments to arrays of strings... */ + let aNames; 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.cstrToJs(wasm.peekPtr(pColVals + offset)) ); - aNames.push( wasm.cstrToJs(wasm.peekPtr(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. */ + const aVals = wasm.cArgvToJs(nCols, pColVals); + if(!aNames) aNames = wasm.cArgvToJs(nCols, pColNames); + rc = 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. */ + /* 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. */ + rc = (e instanceof sqlite3.WasmAllocError) + ? capi.SQLITE_NOMEM + : (e.resultCode || capi.SQLITE_ERROR); } return rc; }; @@ -616,7 +616,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; }/*sqlite3_exec() proxy*/; - if(1){/* Special-case handling of sqlite3_create_function_v2() + {/* Special-case handling of sqlite3_create_function_v2() and sqlite3_create_window_function() */ /* Maintenance reminder: FuncPtrAdapter is not expressive enough to be able to perform these mappings. */ @@ -1041,20 +1041,21 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", "static buffer size!"); } - wasm.ctype = JSON.parse(wasm.cstrToJs(cJson)); //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', 'config', 'dataTypes', 'dbConfig', 'dbStatus', 'encodings', 'fcntl', 'flock', 'ioCap', 'limits', 'openFlags', 'prepareFlags', 'resultCodes', - 'serialize', 'sqlite3Status', + 'sqlite3Status', 'stmtStatus', 'syncFlags', 'trace', 'txnState', 'udfFlags', 'version' ]; if(wasm.bigIntEnabled){ - defineGroups.push('vtab'); + defineGroups.push('serialize', 'vtab'); } for(const t of defineGroups){ for(const e of Object.entries(wasm.ctype[t])){ -- cgit v1.2.3 From 485229e14774ef12d31a815b05aee047739086dd Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 25 Dec 2022 12:51:53 +0000 Subject: Enhance sqlite3.wasm.xWrap.FuncPtrAdapter to be able to handle sqlite3_create_function() and friends and reimplement those bindings to use this feature (this will also simplify certain session API bindings). Interal API changes only with no client-side breakage. FossilOrigin-Name: 7f9ace1b11a6703031790af9cf08ab25df25850a86e6ca2a7aeaefd8aa395e6d --- ext/wasm/api/sqlite3-api-glue.js | 241 ++++++++++++++++++--------------------- 1 file changed, 112 insertions(+), 129 deletions(-) (limited to 'ext/wasm/api/sqlite3-api-glue.js') diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 0ef796664..9066b3431 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -82,7 +82,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "sqlite3*", new wasm.xWrap.FuncPtrAdapter({ signature: 'i(pi)', - contextKey: (argIndex,argv)=>'sqlite3@'+argv[0] + contextKey: (argv,argIndex)=>'sqlite3@'+argv[0] }), "*" ]], @@ -160,7 +160,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ name: 'xProgressHandler', signature: 'i(p)', bindScope: 'context', - contextKey: (argIndex,argv)=>'sqlite3@'+argv[0] + contextKey: (argv,argIndex)=>'sqlite3@'+argv[0] }), "*" ] ], @@ -200,7 +200,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ new wasm.xWrap.FuncPtrAdapter({ name: 'sqlite3_trace_v2::callback', signature: 'i(ippp)', - contextKey: (argIndex, argv)=>'sqlite3@'+argv[0] + contextKey: (argv,argIndex)=>'sqlite3@'+argv[0] }), "*"], ["sqlite3_txn_state", "int", ["sqlite3*","string"]], /* Note that sqlite3_uri_...() have very specific requirements for @@ -497,28 +497,24 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ); }; - if(1){/* Bindings for sqlite3_create_collation[_v2]() */ - const __collationContextKey = (argIndex,argv)=>{ + {/* Bindings for sqlite3_create_collation[_v2]() */ + // contextKey() impl for wasm.xWrap.FuncPtrAdapter + const contextKey = (argv,argIndex)=>{ return 'argv['+argIndex+']:sqlite3@'+argv[0]+ ':'+wasm.cstrToJs(argv[1]).toLowerCase() }; - const __ccv2 = wasm.xWrap( - 'sqlite3_create_collation_v2', 'int', - 'sqlite3*','string','int','*', - new wasm.xWrap.FuncPtrAdapter({ - /* int(*xCompare)(void*,int,const void*,int,const void*) */ - name: 'sqlite3_create_collation_v2::xCompare', - signature: 'i(pipip)', - bindScope: 'context', - contextKey: __collationContextKey - }), - new wasm.xWrap.FuncPtrAdapter({ - /* void(*xDestroy(void*) */ - name: 'sqlite3_create_collation_v2::xDestroy', - signature: 'v(p)', - bindScope: 'context', - contextKey: __collationContextKey - }) + 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 + }) + ] ); /** @@ -552,13 +548,11 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ return __errEncoding(pDb); } - let rc, pfCompare, pfDestroy; - try{ - rc = __ccv2(pDb, zName, eTextRep, pArg, xCompare, xDestroy); + try{ + return __sqlite3CreateCollationV2(pDb, zName, eTextRep, pArg, xCompare, xDestroy); }catch(e){ - rc = util.sqlite3_wasm_db_error(pDb, e); + return util.sqlite3_wasm_db_error(pDb, e); } - return rc; }; capi.sqlite3_create_collation = (pDb,zName,eTextRep,pArg,xCompare)=>{ @@ -617,82 +611,91 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/*sqlite3_exec() proxy*/; {/* Special-case handling of sqlite3_create_function_v2() - and sqlite3_create_window_function() */ - /* Maintenance reminder: FuncPtrAdapter is not expressive enough - to be able to perform these mappings. */ - const sqlite3CreateFunction = wasm.xWrap( - "sqlite3_create_function_v2", "int", - ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, - "int"/*eTextRep*/, "*"/*pApp*/, - "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, "*"/*xDestroy*/] - ); - - const sqlite3CreateWindowFunction = wasm.xWrap( - "sqlite3_create_window_function", "int", - ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, - "int"/*eTextRep*/, "*"/*pApp*/, - "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, - "*"/*xInverse*/, "*"/*xDestroy*/] - ); - - const __xFunc = function(callback){ - return function(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); - } - }; - }; - - const __xInverseAndStep = function(callback){ - return function(pCtx, argc, pArgv){ - try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) } - catch(e){ capi.sqlite3_result_error_js(pCtx, e) } - }; - }; - - const __xFinalAndValue = function(callback){ - return function(pCtx){ - try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) } - catch(e){ capi.sqlite3_result_error_js(pCtx, e) } - }; - }; - - const __xDestroy = function(callback){ - return function(pVoid){ - try{ callback(pVoid) } - catch(e){ console.error("UDF xDestroy method threw:",e) } - }; + and sqlite3_create_window_function(). */ + /** + FuncPtrAdapter for contextKey() for sqlite3_create_function(). + */ + const contextKey = function(argv,argIndex){ + return ( + 'sqlite3@'+argv[0] + +':'+argIndex + +':'+wasm.cstrToJs(argv[1]).toLowerCase() + ) }; - const __xMap = Object.assign(Object.create(null), { - xFunc: {sig:'v(pip)', f:__xFunc}, - xStep: {sig:'v(pip)', f:__xInverseAndStep}, - xInverse: {sig:'v(pip)', f:__xInverseAndStep}, - xFinal: {sig:'v(p)', f:__xFinalAndValue}, - xValue: {sig:'v(p)', f:__xFinalAndValue}, - xDestroy: {sig:'v(p)', f:__xDestroy} - }); - - /* Internal helper for sqlite3_create_function() and friends. */ - const __xWrapFuncs = function(theKeys, theFuncs, tgtUninst){ - const rc = [] - for(const k of theKeys){ - let fArg = theFuncs[k]; - if('function'===typeof fArg){ - const w = __xMap[k] || toss3("Internal error in __xWrapFuncs: invalid key:",k); - fArg = wasm.installFunction(w.sig, w.f(fArg)); - tgtUninst.push(fArg); + /** + 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) } + }; } - rc.push(fArg); } - return rc; - }; + })/*__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( @@ -709,26 +712,16 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ return __errEncoding(pDb); } - /* Wrap the callbacks in a WASM-bound functions... */ - const uninstall = [/*funcs to uninstall on error*/]; - let rc; try{ - const funcArgs = __xWrapFuncs(['xFunc','xStep','xFinal','xDestroy'], - {xFunc, xStep, xFinal, xDestroy}, - uninstall); - rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, - pApp, ...funcArgs); + return __sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, + pApp, xFunc, xStep, xFinal, xDestroy); }catch(e){ console.error("sqlite3_create_function_v2() setup threw:",e); - for(let v of uninstall){ - wasm.uninstallFunction(v); - } - rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, - "Creation of UDF threw: "+e.message); + return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } - return rc; }; + /* Documented in the api object's initializer. */ capi.sqlite3_create_function = function f( pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal @@ -744,8 +737,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ pDb, funcName, nArg, eTextRep, pApp, xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) xFinal, //void (*xFinal)(sqlite3_context*) - xValue, //void (*xFinal)(sqlite3_context*) - xInverse,//void (*xStep)(sqlite3_context*,int,sqlite3_value**) + xValue, //void (*xValue)(sqlite3_context*) + xInverse,//void (*xInverse)(sqlite3_context*,int,sqlite3_value**) xDestroy //void (*xDestroy)(void*) ){ if( f.length!==arguments.length ){ @@ -755,24 +748,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ return __errEncoding(pDb); } - /* Wrap the callbacks in a WASM-bound functions... */ - const uninstall = [/*funcs to uninstall on error*/]; - let rc; try{ - const funcArgs = __xWrapFuncs(['xStep','xFinal','xValue','xInverse','xDestroy'], - {xStep, xFinal, xValue, xInverse, xDestroy}, - uninstall); - rc = sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep, - pApp, ...funcArgs); + return __sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep, + pApp, xStep, xFinal, xValue, + xInverse, xDestroy); }catch(e){ console.error("sqlite3_create_window_function() setup threw:",e); - for(let v of uninstall){ - wasm.uninstallFunction(v); - } - rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, - "Creation of UDF threw: "+e.message); + return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } - return rc; }; /** A _deprecated_ alias for capi.sqlite3_result_js() which -- cgit v1.2.3 From 7015aa9f4957c3131c8bc814c81c4c5d2dea8f89 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 25 Dec 2022 14:04:06 +0000 Subject: Add missing sqlite3_context_db_handle() JS binding. Reimplement sqlite3_set_authorizer() and sqlite3_set_auxdata() JS bindings to take advantage of [7f9ace1b11a67]. Teach FuncPtrAdapter to emit a console.warn() message if it is invoked after the library is bootstrapped, the goal being to inform users that it's an internal API and should not be invoked from client-side code. FossilOrigin-Name: 8e3d4f6294037396e388ec21abb18bf0201a6bec6ff004730cc5d11b705a6d2b --- ext/wasm/api/sqlite3-api-glue.js | 114 +++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 60 deletions(-) (limited to 'ext/wasm/api/sqlite3-api-glue.js') diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 9066b3431..85c549ae3 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -82,7 +82,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "sqlite3*", new wasm.xWrap.FuncPtrAdapter({ signature: 'i(pi)', - contextKey: (argv,argIndex)=>'sqlite3@'+argv[0] + contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] }), "*" ]], @@ -103,6 +103,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(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. */ @@ -154,16 +156,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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)=>'sqlite3@'+argv[0] - }), "*" - ] - ], + ["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_result_blob", undefined, "sqlite3_context*", "*", "int", "*"], @@ -179,7 +179,34 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["sqlite3_result_subtype", undefined, "sqlite3_value*", "int"], ["sqlite3_result_text", undefined, "sqlite3_context*", "string", "int", "*"], ["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"], - ["sqlite3_set_auxdata", undefined, "sqlite3_context*", "int", "*", "*"/* => v(*) */], + ["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_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*"], @@ -196,12 +223,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "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)=>'sqlite3@'+argv[0] - }), "*"], + ["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 @@ -267,8 +297,6 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["sqlite3_result_int64", undefined, "*", "i64"], ["sqlite3_result_zeroblob64", "int", "*", "i64"], ["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"], - /* sqlite3_set_authorizer() requires a hand-written binding for - string conversions, so is defined elsewhere. */ ["sqlite3_set_last_insert_rowid", undefined, ["sqlite3*", "i64"]], ["sqlite3_status64", "int", "int", "*", "*", "int"], ["sqlite3_total_changes64", "i64", ["sqlite3*"]], @@ -500,8 +528,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ {/* Bindings for sqlite3_create_collation[_v2]() */ // contextKey() impl for wasm.xWrap.FuncPtrAdapter const contextKey = (argv,argIndex)=>{ - return 'argv['+argIndex+']:sqlite3@'+argv[0]+ - ':'+wasm.cstrToJs(argv[1]).toLowerCase() + return 'argv['+argIndex+']:'+argv[0/* sqlite3* */]+ + ':'+wasm.cstrToJs(argv[1/* collation name */]).toLowerCase() }; const __sqlite3CreateCollationV2 = wasm.xWrap( 'sqlite3_create_collation_v2', 'int', [ @@ -581,11 +609,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ `(char**)` arguments to arrays of strings... */ let aNames; const cbwrap = function(pVoid, nCols, pColVals, pColNames){ - let rc = capi.SQLITE_ERROR; try { const aVals = wasm.cArgvToJs(nCols, pColVals); if(!aNames) aNames = wasm.cArgvToJs(nCols, pColNames); - rc = callback(aVals, aNames) | 0; + 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 @@ -593,11 +620,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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. */ - rc = (e instanceof sqlite3.WasmAllocError) - ? capi.SQLITE_NOMEM - : (e.resultCode || capi.SQLITE_ERROR); + return e.resultCode || capi.SQLITE_ERROR; } - return rc; }; let rc; try{ @@ -617,7 +641,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ const contextKey = function(argv,argIndex){ return ( - 'sqlite3@'+argv[0] + argv[0/* sqlite3* */] +':'+argIndex +':'+wasm.cstrToJs(argv[1]).toLowerCase() ) @@ -941,37 +965,6 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/*sqlite3_bind_text/blob()*/ - {/* sqlite3_set_authorizer() */ - const __ssa = wasm.xWrap("sqlite3_set_authorizer", 'int', [ - "sqlite3*", - new wasm.xWrap.FuncPtrAdapter({ - name: "sqlite3_set_authorizer::xAuth", - signature: "i(pi"+"ssss)", - contextKey: (argIndex, argv)=>argv[0/*(sqlite3*)*/] - }), - "*" - ]); - capi.sqlite3_set_authorizer = function(pDb, xAuth, pUserData){ - if(3!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_set_authorizer', 3); - if(xAuth instanceof Function){ - const xProxy = xAuth; - /* Create a proxy which will receive the C-strings from WASM - and convert them to JS strings for the client-supplied - function. */ - xAuth = function(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 xProxy(pV, iCode, s0, s1, s2, s3) || 0; - }catch(e){ - return util.sqlite3_wasm_db_error(pDb, e); - } - }; - } - return __ssa(pDb, xAuth, pUserData); - }; - }/* sqlite3_set_authorizer() */ - {/* sqlite3_config() */ /** Wraps a small subset of the C API's sqlite3_config() options. @@ -1190,4 +1183,5 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } }/*pKvvfs*/ + wasm.xWrap.FuncPtrAdapter.warnOnUse = true; }); -- cgit v1.2.3