Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch wasm-fts5 Excluding Merge-Ins
This is equivalent to a diff from ea0b9aecba to ce2a65d80f
2023-08-04
| ||
08:39 | More work towards fts5 customzation in JS. (Leaf check-in: ce2a65d80f user: stephan tags: wasm-fts5) | |
2023-08-03
| ||
22:43 | Minor internal cleanups in the JS-side fts5 cleanup steps. (check-in: 2666f52e88 user: stephan tags: wasm-fts5) | |
12:41 | Unix builds now assume the presence of nanosleep() in the standard library. The -DHAVE_NANOSLEEP=0 compile-time option can be used to build on systems (if any still exist) where this is not the case. (check-in: 779d5dc879 user: drh tags: trunk) | |
07:20 | Initial work on exposing the FTS5 APIs to wasm, per request in the forum. This builds and loads the structs into JS but is completely untested. (check-in: 52c8b73ae3 user: stephan tags: wasm-fts5) | |
2023-08-02
| ||
18:20 | If a query has an ORDER BY clause that only refers to result columns of the left-most table and the left most table is a MATERIALIZED common table expresion, then attempt to push the ORDER BY clause down into the subquery. (Leaf check-in: 8e7a70b2bb user: drh tags: order-by-push-down) | |
16:06 | Performance optimization for JSON rendering logic. (check-in: ea0b9aecba user: drh tags: trunk) | |
13:45 | Remove an unreachable branch in the ascii-to-floating-point conversion that was added by [e989a37ff9d5b52e]. (check-in: c4347e4400 user: drh tags: trunk) | |
Changes to ext/wasm/GNUmakefile.
︙ | ︙ | |||
345 346 347 348 349 350 351 352 353 354 355 356 357 358 | # EXPORTED_FUNCTIONS.* = files for use with Emscripten's # -sEXPORTED_FUNCTION flag. EXPORTED_FUNCTIONS.api.main := $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) EXPORTED_FUNCTIONS.api.in := $(EXPORTED_FUNCTIONS.api.main) ifeq (1,$(SQLITE_C_IS_SEE)) EXPORTED_FUNCTIONS.api.in += $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-see) endif EXPORTED_FUNCTIONS.api := $(dir.tmp)/EXPORTED_FUNCTIONS.api $(EXPORTED_FUNCTIONS.api): $(EXPORTED_FUNCTIONS.api.in) $(sqlite3.c) $(MAKEFILE) cat $(EXPORTED_FUNCTIONS.api.in) > $@ # sqlite3-license-version.js = generated JS file with the license # header and version info. sqlite3-license-version.js := $(dir.tmp)/sqlite3-license-version.js | > > > > > > | 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 | # EXPORTED_FUNCTIONS.* = files for use with Emscripten's # -sEXPORTED_FUNCTION flag. EXPORTED_FUNCTIONS.api.main := $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api) EXPORTED_FUNCTIONS.api.in := $(EXPORTED_FUNCTIONS.api.main) ifeq (1,$(SQLITE_C_IS_SEE)) EXPORTED_FUNCTIONS.api.in += $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-see) endif ifeq (1,$(emcc.WASM_BIGINT)) ifneq (,$(filter -DSQLITE_ENABLE_FTS5,$(SQLITE_OPT))) EXPORTED_FUNCTIONS.api.in += $(abspath $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-fts5) endif endif EXPORTED_FUNCTIONS.api := $(dir.tmp)/EXPORTED_FUNCTIONS.api $(EXPORTED_FUNCTIONS.api): $(EXPORTED_FUNCTIONS.api.in) $(sqlite3.c) $(MAKEFILE) cat $(EXPORTED_FUNCTIONS.api.in) > $@ # sqlite3-license-version.js = generated JS file with the license # header and version info. sqlite3-license-version.js := $(dir.tmp)/sqlite3-license-version.js |
︙ | ︙ | |||
371 372 373 374 375 376 377 378 379 380 381 382 383 384 | sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.js sqlite3-api.jses += $(sqlite3-api-build-version.js) sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-sahpool.c-pp.js sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js # SOAP.js is an external API file which is part of our distribution # but not part of the sqlite3-api.js amalgamation. SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js SOAP.js.bld := $(dir.dout)/$(notdir $(SOAP.js)) sqlite3-api.ext.jses += $(SOAP.js.bld) | > > > | 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 | sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.js sqlite3-api.jses += $(sqlite3-api-build-version.js) sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-sahpool.c-pp.js ifneq (,$(filter -DSQLITE_ENABLE_FTS5,$(SQLITE_OPT))) sqlite3-api.jses += $(dir.api)/sqlite3-fts5-helper.js endif sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js # SOAP.js is an external API file which is part of our distribution # but not part of the sqlite3-api.js amalgamation. SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js SOAP.js.bld := $(dir.dout)/$(notdir $(SOAP.js)) sqlite3-api.ext.jses += $(SOAP.js.bld) |
︙ | ︙ |
Added ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-fts5.
> | 1 | _fts5_api_from_db |
Changes to ext/wasm/api/sqlite3-api-cleanup.js.
︙ | ︙ | |||
48 49 50 51 52 53 54 | }catch(e){ console.error("sqlite3ApiBootstrap() error:",e); throw e; }finally{ delete globalThis.sqlite3ApiBootstrap; delete globalThis.sqlite3ApiConfig; } | | > | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | }catch(e){ console.error("sqlite3ApiBootstrap() error:",e); throw e; }finally{ delete globalThis.sqlite3ApiBootstrap; delete globalThis.sqlite3ApiConfig; } delete sqlite3.__dbCleanupMap; Module.sqlite3 = sqlite3 /* Needed for customized sqlite3InitModule() to be able to pass the sqlite3 object off to the client. */; sqlite3.wasm.xWrap.FuncPtrAdapter.warnOnUse = true; }else{ console.warn("This is not running in an Emscripten module context, so", "globalThis.sqlite3ApiBootstrap() is _not_ being called due to lack", "of config info for the WASM environment.", "It must be called manually."); } |
Changes to ext/wasm/api/sqlite3-api-glue.js.
︙ | ︙ | |||
257 258 259 260 261 262 263 264 265 266 267 268 269 270 | 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* */] }) | > > > > > > > > > > > > | 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | return e.resultCode || capi.SQLITE_ERROR; } } } }), "*"/*pUserData*/ ]], /** Achtung: when specifying an xDestroy() method via sqlite3_set_auxdata(), it is up to the client to re-set it to 0/NULL at the end of its lifetime (e.g. in the associated UDF's xFinal() impl), The C library will be able to call the destructor but _not_ uninstall the temporary WASM-bound proxy function because it does not have enough information to do so. Alternately, clients may create the function pointer themselves using wasm.createFunction() and pass that pointer here, in which case they avoid creating a stranded "temporary" function binding. */ ["sqlite3_set_auxdata", undefined, [ "sqlite3_context*", "int", "*", new wasm.xWrap.FuncPtrAdapter({ name: 'xDestroyAuxData', signature: 'v(*)', contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */] }) |
︙ | ︙ | |||
417 418 419 420 421 422 423 | ["sqlite3_vtab_nochange","int", "sqlite3_context*"], ["sqlite3_vtab_on_conflict","int", "sqlite3*"], ["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"] ]; // Add session/changeset APIs... if(wasm.bigIntEnabled && !!wasm.exports.sqlite3changegroup_add){ | < < | 429 430 431 432 433 434 435 436 437 438 439 440 441 442 | ["sqlite3_vtab_nochange","int", "sqlite3_context*"], ["sqlite3_vtab_on_conflict","int", "sqlite3*"], ["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"] ]; // Add session/changeset APIs... if(wasm.bigIntEnabled && !!wasm.exports.sqlite3changegroup_add){ /** 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 = { |
︙ | ︙ | |||
593 594 595 596 597 598 599 600 601 602 603 604 605 606 | 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.wasm. Some of them get exposed to clients via variants named sqlite3_js_...(). */ wasm.bindingSignatures.wasm = [ | > > > > > > | 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 | contextKey: (argv,argIndex)=>argv[0/* (sqlite3_session*) */] }), '*' ]] ]); }/*session/changeset APIs*/ if(wasm.bigIntEnabled && !!wasm.exports.fts5_api_from_db){ wasm.bindingSignatures.int64.push( ['fts5_api_from_db', 'fts5_api*', 'sqlite3*'] ); }/* fts5 APIs */ /** 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 = [ |
︙ | ︙ | |||
655 656 657 658 659 660 661 | need it to. */ wasm.xWrap.argAdapter( 'string:static', function(v){ if(wasm.isPtr(v)) return v; v = ''+v; | | | | 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 | need it to. */ wasm.xWrap.argAdapter( 'string:static', function(v){ if(wasm.isPtr(v)) return v; v = ''+v; const rc = this[v]; return (undefined===rc) ? (this[v] = wasm.allocCString(v)) : rc; }.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 |
︙ | ︙ | |||
713 714 715 716 717 718 719 | ); } return __xArgPtr((v instanceof (capi.sqlite3_vfs || nilType)) ? v.pointer : v); }); const __xRcPtr = wasm.xWrap.resultAdapter('*'); | | > > | 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 | ); } return __xArgPtr((v instanceof (capi.sqlite3_vfs || nilType)) ? v.pointer : v); }); const __xRcPtr = wasm.xWrap.resultAdapter('*'); wasm.xWrap.resultAdapter ('fts5_api*', __xRcPtr) ('sqlite3*', __xRcPtr) ('sqlite3_context*', __xRcPtr) ('sqlite3_stmt*', __xRcPtr) ('sqlite3_value*', __xRcPtr) ('sqlite3_vfs*', __xRcPtr) ('void*', __xRcPtr); /** |
︙ | ︙ | |||
819 820 821 822 823 824 825 826 827 828 829 830 831 832 | '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]; } | > > > | 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 | 'prepareFlags', 'resultCodes', 'sqlite3Status', 'stmtStatus', 'syncFlags', 'trace', 'txnState', 'udfFlags', 'version' ]; if(wasm.bigIntEnabled){ defineGroups.push('serialize', 'session', 'vtab'); if(!!wasm.ctype.fts5){ defineGroups.push('fts5'); } } 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]; } |
︙ | ︙ | |||
902 903 904 905 906 907 908 | }; /** __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. */ | | | | 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 | }; /** __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 = wasm.xWrap.argAdapter('sqlite3*'); const __argStr = (str)=>wasm.isPtr(str) ? wasm.cstrToJs(str) : str; const __dbCleanupMap = sqlite3.__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; |
︙ | ︙ | |||
969 970 971 972 973 974 975 976 977 978 979 980 981 982 | 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. | > | 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 | 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); //console.warn("db cleanup",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. |
︙ | ︙ | |||
996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 | closeArgs.length = x.length/*==argument count*/ /* recall that undefined entries translate to 0 when passed to WASM. */; try{ capi[name](...closeArgs) } catch(e){ console.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( | > > > | 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 | closeArgs.length = x.length/*==argument count*/ /* recall that undefined entries translate to 0 when passed to WASM. */; try{ capi[name](...closeArgs) } catch(e){ console.warn("close-time call of",name+"(",closeArgs,") threw:",e); } } for(const callback of __dbCleanupMap.extraCallbacks){ callback(pDb); } const m = __dbCleanupMap(pDb, 0); if(!m) return; if(m.collation){ for(const name of m.collation){ try{ capi.sqlite3_create_collation_v2( |
︙ | ︙ | |||
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 | 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*/} } | > > > > > > > > > > > > > > > > > > | > > > > > > | 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 | arities.clear(); } fmap.clear(); } delete m.udf; delete m.wudf; }/*__dbCleanupMap.cleanup()*/; /** Downstream code, namely sqlite3-fts5-helper.js, should add any custom cleanup handlers to __dbCleanupMap.extraCallbacks. Each function in this array will be called during sqlite3_close_v2() and passed a pointer to the being-destroyed (sqlite3*) object. */ __dbCleanupMap.extraCallbacks = []; /** Downstream code, namely sqlite3-fts5-helper.js, should add any custom cleanup handlers to __dbCleanupMap.postCloseCallbacks. Each function in this array will be called during sqlite3_close_v2(), AFTER the db is closed, and passed a pointer to the being-destroyed (sqlite3*) object. The memory is NOT A VALID OBJECT but its address is still valid as a lookup key. */ __dbCleanupMap.postCloseCallbacks = []; {/* Binding of sqlite3_close_v2() */ const __sqlite3CloseV2 = wasm.xWrap("sqlite3_close_v2", "int", "sqlite3*"); const __xArgDb = wasm.xWrap.argAdapter('sqlite3*'); capi.sqlite3_close_v2 = function(pDb){ if(1!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_close_v2', 1); pDb = __xArgDb(pDb); if(pDb){ try{__dbCleanupMap.cleanup(pDb)} catch(e){/*ignored*/} } const rc = __sqlite3CloseV2(pDb); if(pDb/*noting that it's not valid anymore*/){ for(const f of __dbCleanupMap.postCloseCallbacks){ try{f(pDb)}catch(e){/*ingored*/} } } return rc; }; }/*sqlite3_close_v2()*/ if(capi.sqlite3session_table_filter){ const __sqlite3SessionDelete = wasm.xWrap( 'sqlite3session_delete', undefined, ['sqlite3_session*'] ); |
︙ | ︙ | |||
1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 | +':'+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)) } | > > > | 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 | +':'+wasm.cstrToJs(argv[1]).toLowerCase() ) }; /** JS proxies for the various sqlite3_create[_window]_function() callbacks, structured in a form usable by wasm.xWrap.FuncPtrAdapter. TODO: explore an API option which more closely resembles the /ext/jni mapping, which is much friendlier at the client level. */ 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)) } |
︙ | ︙ | |||
1661 1662 1663 1664 1665 1666 1667 | /* 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*/ | < | 1713 1714 1715 1716 1717 1718 1719 1720 | /* 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*/ }); |
Changes to ext/wasm/api/sqlite3-api-prologue.js.
︙ | ︙ | |||
1641 1642 1643 1644 1645 1646 1647 | Curiously: despite ostensibly requiring 8-byte alignment, the pArgv array is parcelled into chunks of 4 bytes (1 pointer each). The values those point to have 8-byte alignment but the individual argv entries do not. */ tgt.push(capi.sqlite3_value_to_js( | | > | 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 | Curiously: despite ostensibly requiring 8-byte alignment, the pArgv array is parcelled into chunks of 4 bytes (1 pointer each). The values those point to have 8-byte alignment but the individual argv entries do not. */ tgt.push(capi.sqlite3_value_to_js( wasm.peekPtr(pArgv + (wasm.ptrSizeof * i)), throwIfCannotConvert )); } return tgt; }; /** Calls either sqlite3_result_error_nomem(), if e is-a |
︙ | ︙ |
Added ext/wasm/api/sqlite3-fts5-helper.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 | /* ** 2023-08-03 ** ** 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 installs sqlite3.fts5, a namespace which exists to assist in JavaScript-side extension of FTS5. */ 'use strict'; globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3; if(!capi.fts5_api_from_db){ return /*this build does not have FTS5*/; } const fts = sqlite3.fts5 = Object.create(null); const __xArgDb = wasm.xWrap.argAdapter('sqlite3*'); /** Move FTS-specific APIs (installed via automation) from sqlite3.capi to sqlite3.fts. */ for(const c of [ 'Fts5ExtensionApi', 'Fts5PhraseIter', 'fts5_api', 'fts5_api_from_db', 'fts5_tokenizer' ]){ fts[c] = capi[c] || toss("Cannot find capi."+c); delete capi[c]; } /** Requires a JS Function intended to be used as an xFunction() implementation. This function returns a proxy xFunction wrapper which: - Converts all of its sqlite3_value arguments to an array of JS values using sqlite3_values_to_js(). - Calls the given callback, passing it: (pFtsXApi, pFtsCx, pCtx, array-of-values) where the first 3 arguments are the first 3 pointers in the xFunction interface. The call is intended to set a result value into the db, and may do so be either (A) explicitly returning non-undefined or (B) using one of the sqlite3_result_XYZ() functions and returning undefined. If the callback throws, its exception will be passed to sqlite3_result_error_js(). */ fts.xFunctionProxy1 = function(callback){ return (pFtsXApi, pFtsCx, pCtx, argc, pArgv)=>{ try{ capi.sqlite3_result_js(pCtx, callback( pFtsXApi, pFtsCx, pCtx, capi.sqlite3_values_to_js(argc, pArgv) )); }catch(e){ capi.sqlite3_result_error_js(pCtx, e); } } }; /** Identical to xFunctionProxy1 except that the callback wrapper it creates does _not_ perform sqlite3_value-to-JS conversion in advance and calls the callback with: (pFtsXApi, pFtsCx, pCtx, array-of-ptr-to-sqlite3_value) It is up to the callback to use the sqlite3_value_XYZ() family of functions to inspect or convert the values. */ fts.xFunctionProxy2 = function(callback){ return (pFtsXApi, pFtsCx, pCtx, argc, pArgv)=>{ try{ const list = []; let i; for(i = 0; i < argc; ++i){ list.push( wasm.peekPtr(pArgv + (wasm.ptrSizeof * i)) ); } capi.sqlite3_result_js(pCtx, callback( pFtsXApi, pFtsCx, pCtx, list )); }catch(e){ capi.sqlite3_result_error_js(pCtx, e); } } }; /** JS-to-WASM arg adapter for xCreateFunction()'s xFunction arg. This binds JS impls of xFunction to WASM so that they can be called from native code. Its context is limited to the combination of ((fts5_api*) + functionNameCaseInsensitive), and will replace any existing impl for subsequent invocations for the same combination. The functions is creates are intended to set a result value into the db, and may do so be either (A) explicitly returning non-undefined or (B) using one of the sqlite3_result_XYZ() functions and returning undefined. If the callback throws, its exception will be passed to sqlite3_result_error_js(). PENDING DESIGN DECISION: this framework currently converts each argument in its JS equivalent before passing them on to the xFunction impl. We could, and possibly should, instead pass a JS array of sqlite3_value pointers. The advantages would be: - No in-advance to-JS overhead which xFunction might not use. Disadvantages include: - xFunction would be required to call sqlite3_value_to_js(), or one of the many sqlite3_value_XYZ() functions on their own. This would be more cumbersome for most users. Regardless of which approach is chosen here, clients could provide a function of their own which takes the _other_ approach, install it with wasm.installFunction(), and then pass that generated pointer to createFunction(), in which case this layer does not proxying and passes all native-level arguments as-is to the client-defined function. */ const xFunctionArgAdapter = new wasm.xWrap.FuncPtrAdapter({ name: 'fts5_api::xCreateFunction(xFunction)', signature: 'v(pppip)', contextKey: (argv,argIndex)=>{ return (argv[0]/*(fts5_api*)*/ + wasm.cstrToJs(argv[1]).toLowerCase()/*name*/) }, callProxy: fts.xFunctionProxy1 }); /** Map of (sqlite3*) to fts.fts5_api. */ const __ftsApiToStruct = Object.create(null); const __fts5_api_from_db = function(pDb, createIfNeeded){ let rc = __ftsApiToStruct[pDb]; if(!rc && createIfNeeded){ const fapi = fts.fts5_api_from_db(pDb) || toss("Internal error - cannot get FTS5 API object for db."); rc = new fts.fts5_api(fapi); __ftsApiToStruct[pDb] = rc; } return rc; }; /** Arrange for WASM functions dynamically created via this API to be uninstalled when the db they were installed for is closed... */ const __addCleanupForFunc = function(sfapi, name, pDestroy){ if(!sfapi.$$cleanup){ sfapi.$$cleanup = []; } sfapi.$$cleanup.push([name.toLowerCase(), pDestroy]); }; /** Callback to be invoked via the JS binding of sqlite3_close_v2(), after the db has been closed, meaning that the argument to this function is not a valid object. We use its address only as a lookup key. */ sqlite3.__dbCleanupMap.postCloseCallbacks.push(function(pDb){ const sfapi = __fts5_api_from_db(pDb, false); if(sfapi){ delete __ftsApiToStruct[pDb]; if(sfapi.$$cleanup){ const fapi = sfapi.pointer; const scope = wasm.scopedAllocPush(); //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; try{ for(const [name, pDestroy] of sfapi.$$cleanup){ try{ /* Uninstall xFunctionArgAdapter's bindings via a roundabout approach: its scoping rules uninstall each new installation at the earliest opportunity, so we simply need to fake a call with a 0-pointer for the xFunction callback to uninstall the most recent one. */ const zName = wasm.scopedAllocCString(name); const argv = [fapi, zName, 0, 0, 0]; xFunctionArgAdapter.convertArg(argv[3], argv, 3); /* xDestroy, on the other hand, requires some hand-holding to ensure we don't prematurely uninstall these when a function is replaced (shadowed). */ if(pDestroy) wasm.uninstallFunction(pDestroy); }catch(e){ sqlite3.config.warn("Could not remove FTS func",name,e); } } }finally{ wasm.scopedAllocPop(scope); } //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false; } sfapi.dispose(); } }); const __affirmDbArg = (arg)=>{ arg = __xArgDb(arg); if(!arg || !wasm.isPtr(arg)) toss("Invalid db argument."); return arg; }; /** Convenience wrapper to fts5_api::xCreateFunction. Creates a new FTS5 function for the given database. The arguments are: - db must be either an sqlite3.oo1.DB instance or a WASM pointer to (sqlite3*). - name: name (JS string) of the function - xFunction either a Function or a pointer to a WASM function. In the former case a WASM-bound wrapper, behaving as documented for fts5.xFunctionProxy1(), gets installed for the life of the given db handle. In the latter case the function is passed-through as-is, with no argument conversion or lifetime tracking. In the former case the function is called as documented for xFunctionProxy1() and in the latter it must return void and is called with args (ptrToFts5ExtensionApi, ptrToFts5Context, ptrToSqlite3Context, int argc, C-array-of-sqlite3_value-pointers). - xDestroy optional Function or pointer to WASM function to call when the binding is destroyed (when the db handle is closed). The function will, in this context, always be passed 0 as its only argument. A passed-in function must, however, have one parameter so that type signature checks will pass. It must return void and must not throw. The 2nd and subsequent aruguments may optionally be packed into a single Object with like-named properties. This function throws on error, of which there are many potential candidates. It returns `undefined`. */ fts.createFunction = function(db, name, xFunction, xDestroy = 0){ db = __affirmDbArg(db); if( 2 === arguments.length && 'string' !== typeof name){ xDestroy = name.xDestroy || null; xFunction = name.xFunction || null; name = name.name; } if( !name || 'string' !== typeof name ) toss("Invalid name argument."); const sfapi = __fts5_api_from_db(db, true); let pDestroy = 0; try{ /** Because of how fts5_api::xCreateFunction() replaces functions (by prepending new ones to a linked list but retaining old ones), we cannot use a FuncPtrAdapter to automatically convert xDestroy, lest we end up uninstalling a bound-to-wasm JS function's wasm pointer before fts5 cleans it up when the db is closed. */ if(xDestroy instanceof Function){ pDestroy = wasm.installFunction(xDestroy, 'v(p)'); } const xcf = sfapi.$$xCreateFunction || ( sfapi.$$xCreateFunction = wasm.xWrap(sfapi.$xCreateFunction, 'int', [ '*', 'string', '*', xFunctionArgAdapter, '*' ]) ); const rc = xcf(sfapi.pointer, name, 0, xFunction || 0, pDestroy || xDestroy || 0 ); if(rc) toss(rc,"FTS5::xCreateFunction() failed."); __addCleanupForFunc(sfapi, name, pDestroy); }catch(e){ if(pDestroy) wasm.uninstallFunction(pDestroy); sfapi.dispose(); throw e; } }; /** ! UNTESTED Convenience wrapper for fts5_api::xCreateTokenizer(). - db = the db to install the tokenizer into. - name = the JS string name of the tokenizer. - pTokenizer = the tokenizer instance, which must be a fts5.fts5_tokenizer instance or a valid WASM pointer to one. - xDestroy = as documented for createFunction(). The C layer makes a bitwise copy of the tokenizer, so any changes made to it after installation will have no effect. Throws on error. */ const createTokenizer = function(db, name, pTokenizer, xDestroy = 0){ db = __affirmDbArg(db); if( 2 === arguments.length && 'string' !== typeof name){ pTokenizer = name.pTokenizer; xDestroy = name.xDestroy || null; name = name.name; } if( !name || 'string' !== typeof name ) toss("Invalid name argument."); if(pTokenizer instanceof fts.fts5_tokenizer){ pTokenizer = pTokenizer.pointer; } if(!pTokenizer || !wasm.isPtr(pTokenizer)){ toss("Invalid pTokenizer argument - must be a valid fts5.fts5_tokenizer", "instance or a WASM pointer to one."); } const sfapi = __fts5_api_from_db(db, true); let pDestroy = 0; const stackPos = wasm.pstack.pointer; try{ if(xDestroy instanceof Function){ pDestroy = wasm.installFunction(xDestroy, 'v(p)'); } const xct = sfapi.$$xCreateTokenizer || ( sfapi.$$xCreateTokenizer = wasm.xWrap(sfapi.$xCreateTokenizer, 'int', [ '*', 'string', '*', '*', '*' /* fts5_api*, const char *zName, void *pContext, fts5_tokenizer *pTokenizer, void(*xDestroy)(void*) */ ]) ); const outPtr = wasm.pstack.allocPtr(); const rc = xct(fapi.pointer, name, 0, pTokenizer, pDestroy || xDestroy || 0 ); if(rc) toss(rc,"FTS5::xCreateFunction() failed."); if(pDestroy) __addCleanupForFunc(sfapi, name, pDestroy); }catch(e){ if(pDestroy) wasm.uninstallFunction(pDestroy); sfapi.dispose(); throw e; }finally{ wasm.pstack.restore(stackPost); } }; //fts.createTokenizer = createTokenizer; }/*sqlite3ApiBootstrap.initializers.push()*/); |
Changes to ext/wasm/api/sqlite3-v-helper.js.
1 2 3 4 5 6 7 8 9 10 11 12 | /* ** 2022-11-30 ** ** 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. */ /** | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* ** 2022-11-30 ** ** 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 installs sqlite3.vfs, an object which exists to assist in the creation of JavaScript implementations of sqlite3_vfs, along with its virtual table counterpart, sqlite3.vtab. */ 'use strict'; globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3; const vfs = Object.create(null), vtab = Object.create(null); |
︙ | ︙ |
Changes to ext/wasm/api/sqlite3-wasm.c.
︙ | ︙ | |||
92 93 94 95 96 97 98 | #endif #ifndef SQLITE_ENABLE_DBSTAT_VTAB # define SQLITE_ENABLE_DBSTAT_VTAB 1 #endif #ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS # define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 #endif | | | | 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | #endif #ifndef SQLITE_ENABLE_DBSTAT_VTAB # define SQLITE_ENABLE_DBSTAT_VTAB 1 #endif #ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS # define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 #endif #ifndef SQLITE_ENABLE_FTS5 # define SQLITE_ENABLE_FTS5 1 #endif #ifndef SQLITE_ENABLE_MATH_FUNCTIONS # define SQLITE_ENABLE_MATH_FUNCTIONS 1 #endif #ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC # define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 #endif |
︙ | ︙ | |||
359 360 361 362 363 364 365 366 367 368 369 370 371 372 | }else{ sqlite3ErrorWithMsg(db, err_code, NULL); } } return err_code; } #if SQLITE_WASM_TESTS struct WasmTestStruct { int v4; void * ppV; const char * cstr; int64_t v8; void (*xFunc)(void*); | > > > > > > > > > > > > > > > > > > > > > | 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 | }else{ sqlite3ErrorWithMsg(db, err_code, NULL); } } return err_code; } #ifdef SQLITE_ENABLE_FTS5 /* ** Return a pointer to the fts5_api pointer for database connection db. ** If an error occurs, return NULL and leave an error in the database ** handle (accessible using sqlite3_errcode()/errmsg()). ** ** This function was taken verbatim from the /fts5.html docs. */ SQLITE_WASM_EXPORT fts5_api *fts5_api_from_db(sqlite3 *db){ fts5_api *pRet = 0; sqlite3_stmt *pStmt = 0; if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0) ){ sqlite3_bind_pointer(pStmt, 1, (void*)&pRet, "fts5_api_ptr", NULL); sqlite3_step(pStmt); } sqlite3_finalize(pStmt); return pRet; } #endif /*SQLITE_ENABLE_FTS5*/ #if SQLITE_WASM_TESTS struct WasmTestStruct { int v4; void * ppV; const char * cstr; int64_t v8; void (*xFunc)(void*); |
︙ | ︙ | |||
398 399 400 401 402 403 404 | ** ** If this function returns NULL then it means that the internal ** buffer is not large enough for the generated JSON and needs to be ** increased. In debug builds that will trigger an assert(). */ SQLITE_WASM_EXPORT const char * sqlite3_wasm_enum_json(void){ | | > > > | 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 | ** ** If this function returns NULL then it means that the internal ** buffer is not large enough for the generated JSON and needs to be ** increased. In debug builds that will trigger an assert(). */ SQLITE_WASM_EXPORT const char * sqlite3_wasm_enum_json(void){ static char aBuffer[1024 * 22] = {0} /* where the JSON goes. If this buffer is not large enough, this function will assert (in debug builds) and return 0. When it does so, this value needs to be increased. */; int n = 0, nChildren = 0, nStruct = 0 /* output counters for figuring out where commas go */; char * zPos = &aBuffer[1] /* skip first byte for now to help protect ** against a small race condition */; char const * const zEnd = &aBuffer[0] + sizeof(aBuffer) /* one-past-the-end */; if(aBuffer[0]) return aBuffer; /* Leave aBuffer[0] at 0 until the end to help guard against a tiny |
︙ | ︙ | |||
646 647 648 649 650 651 652 653 654 655 656 657 658 659 | DefGroup(flock) { DefInt(SQLITE_LOCK_NONE); DefInt(SQLITE_LOCK_SHARED); DefInt(SQLITE_LOCK_RESERVED); DefInt(SQLITE_LOCK_PENDING); DefInt(SQLITE_LOCK_EXCLUSIVE); } _DefGroup; DefGroup(ioCap) { DefInt(SQLITE_IOCAP_ATOMIC); DefInt(SQLITE_IOCAP_ATOMIC512); DefInt(SQLITE_IOCAP_ATOMIC1K); DefInt(SQLITE_IOCAP_ATOMIC2K); DefInt(SQLITE_IOCAP_ATOMIC4K); | > > > > > > > > > > | 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 | DefGroup(flock) { DefInt(SQLITE_LOCK_NONE); DefInt(SQLITE_LOCK_SHARED); DefInt(SQLITE_LOCK_RESERVED); DefInt(SQLITE_LOCK_PENDING); DefInt(SQLITE_LOCK_EXCLUSIVE); } _DefGroup; #ifdef SQLITE_ENABLE_FTS5 DefGroup(fts5) { DefInt(FTS5_TOKENIZE_QUERY); DefInt(FTS5_TOKENIZE_PREFIX); DefInt(FTS5_TOKENIZE_DOCUMENT); DefInt(FTS5_TOKENIZE_AUX); DefInt(FTS5_TOKEN_COLOCATED); } _DefGroup; #endif DefGroup(ioCap) { DefInt(SQLITE_IOCAP_ATOMIC); DefInt(SQLITE_IOCAP_ATOMIC512); DefInt(SQLITE_IOCAP_ATOMIC1K); DefInt(SQLITE_IOCAP_ATOMIC2K); DefInt(SQLITE_IOCAP_ATOMIC4K); |
︙ | ︙ | |||
872 873 874 875 876 877 878 | DefInt(SQLITE_STMTSTATUS_VM_STEP); DefInt(SQLITE_STMTSTATUS_REPREPARE); DefInt(SQLITE_STMTSTATUS_RUN); DefInt(SQLITE_STMTSTATUS_FILTER_MISS); DefInt(SQLITE_STMTSTATUS_FILTER_HIT); DefInt(SQLITE_STMTSTATUS_MEMUSED); } _DefGroup; | | | 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 | DefInt(SQLITE_STMTSTATUS_VM_STEP); DefInt(SQLITE_STMTSTATUS_REPREPARE); DefInt(SQLITE_STMTSTATUS_RUN); DefInt(SQLITE_STMTSTATUS_FILTER_MISS); DefInt(SQLITE_STMTSTATUS_FILTER_HIT); DefInt(SQLITE_STMTSTATUS_MEMUSED); } _DefGroup; DefGroup(syncFlags) { DefInt(SQLITE_SYNC_NORMAL); DefInt(SQLITE_SYNC_FULL); DefInt(SQLITE_SYNC_DATAONLY); } _DefGroup; DefGroup(trace) { |
︙ | ︙ | |||
1090 1091 1092 1093 1094 1095 1096 | M(xSavepoint, "i(pi)"); M(xRelease, "i(pi)"); M(xRollbackTo, "i(pi)"); // ^^^ v2. v3+ follows... M(xShadowName, "i(s)"); } _StructBinder; #undef CurrentStruct | | | 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 | M(xSavepoint, "i(pi)"); M(xRelease, "i(pi)"); M(xRollbackTo, "i(pi)"); // ^^^ v2. v3+ follows... M(xShadowName, "i(s)"); } _StructBinder; #undef CurrentStruct /** ** Workaround: in order to map the various inner structs from ** sqlite3_index_info, we have to uplift those into constructs we ** can access by type name. These structs _must_ match their ** in-sqlite3_index_info counterparts byte for byte. */ typedef struct { |
︙ | ︙ | |||
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 | M(estimatedCost, "d"); M(estimatedRows, "j"); M(idxFlags, "i"); M(colUsed, "j"); } _StructBinder; #undef CurrentStruct #if SQLITE_WASM_TESTS #define CurrentStruct WasmTestStruct StructBinder { M(v4, "i"); M(cstr, "s"); M(ppV, "p"); M(v8, "j"); | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 | M(estimatedCost, "d"); M(estimatedRows, "j"); M(idxFlags, "i"); M(colUsed, "j"); } _StructBinder; #undef CurrentStruct #ifdef SQLITE_ENABLE_FTS5 #define CurrentStruct Fts5PhraseIter StructBinder { M(a, "p"); M(b, "p"); } _StructBinder; #undef CurrentStruct #define CurrentStruct Fts5ExtensionApi StructBinder { M(iVersion, "i"); M(xUserData, "p(p)");// void *(*)(Fts5Context*); M(xColumnCount, "i(p)");// int (*)(Fts5Context*); M(xRowCount, "i(pp)"); //^^^ int (*)(Fts5Context*, sqlite3_int64 *pnRow); M(xColumnTotalSize, "i(pip)"); //^^^ int (*)(Fts5Context*, int iCol, sqlite3_int64 *pnToken); M(xTokenize, "i(ppipp)"); //^^^ int (*)(Fts5Context*, // const char *pText, int nText, /* Text to tokenize */ // void *pCtx, /* Context passed to xToken() */ // int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ //); M(xPhraseCount, "i(p)"); // int (*)(Fts5Context*); M(xPhraseSize, "i(pi)"); // int (*)(Fts5Context*, int iPhrase); M(xInstCount, "i(pp)"); // int (*)(Fts5Context*, int *pnInst); M(xInst, "i(pippp)"); //^^^ int (*)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff); M(xRowid, "j(p)"); // sqlite3_int64 (*)(Fts5Context*); M(xColumnText, "i(pipp)"); //^^^ int (*)(Fts5Context*, int iCol, const char **pz, int *pn); M(xColumnSize, "i(pip)"); //^^^ int (*)(Fts5Context*, int iCol, int *pnToken); M(xQueryPhrase, "i(pipp)"); //^^^ int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData, // int(*)(const Fts5ExtensionApi*,Fts5Context*,void*) // ); M(xSetAuxdata, "i(ppp)"); //^^^ int (*)(Fts5Context*, void *pAux, void(*xDelete)(void*)); M(xGetAuxdata, "p(pi)"); // void *(*)(Fts5Context*, int bClear); M(xPhraseFirst, "i(pippp)"); //^^^ int (*)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); M(xPhraseNext, "v(pppp)"); //^^^ void (*)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff); M(xPhraseFirstColumn, "i(pipp)"); //^^^ int (*)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); M(xPhraseNextColumn, "v(ppp)"); //^^^ void (*)(Fts5Context*, Fts5PhraseIter*, int *piCol); } _StructBinder; #undef CurrentStruct #define CurrentStruct fts5_api StructBinder { M(iVersion, "i");/* Currently always 2 */ M(xCreateTokenizer, "i(ppppp)"); //^^^ int (*)( // fts5_api *pApi, // const char *zName, // void *pContext, // fts5_tokenizer *pTokenizer, // void (*xDestroy)(void*) // ); M(xFindTokenizer, "i(pppp)"); //^^^ int (*)( // fts5_api *pApi, // const char *zName, // void **ppContext, // fts5_tokenizer *pTokenizer // ); M(xCreateFunction, "i(ppppp)"); //^^^ int (*)( // fts5_api *pApi, // const char *zName, // void *pContext, // fts5_extension_function xFunction, // void (*xDestroy)(void*) // ); } _StructBinder; #undef CurrentStruct #define CurrentStruct fts5_tokenizer StructBinder { M(xCreate, "i(ppip)"); //^^^ int (*)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); M(xDelete, "v(p)"); //^^^ void(Fts5Tokenizer*) M(xTokenize, "i(ppipip)"); /**^^^ int (*xTokenize)(Fts5Tokenizer*, void *pCtx, int flags, const char *pText, int nText, int (*xToken)( void *pCtx, int tflags, const char *pToken, int nToken, int iStart, int iEnd ) ); */ } _StructBinder; #undef CurrentStruct #endif /* SQLITE_ENABLE_FTS5 */ #if SQLITE_WASM_TESTS #define CurrentStruct WasmTestStruct StructBinder { M(v4, "i"); M(cstr, "s"); M(ppV, "p"); M(v8, "j"); |
︙ | ︙ |
Changes to ext/wasm/common/whwasmutil.js.
︙ | ︙ | |||
609 610 611 612 613 614 615 | breaking clients which do not take care to avoid that case: https://github.com/emscripten-core/emscripten/issues/17323 */ target.installFunction = (func, sig)=>__installFunction(func, sig, false); /** | < < | 609 610 611 612 613 614 615 616 617 618 619 620 621 622 | breaking clients which do not take care to avoid that case: https://github.com/emscripten-core/emscripten/issues/17323 */ target.installFunction = (func, sig)=>__installFunction(func, sig, false); /** Works exactly like installFunction() but requires that a scopedAllocPush() is active and uninstalls the given function when that alloc scope is popped via scopedAllocPop(). This is used for implementing JS/WASM function bindings which should only persist for the life of a call into a single C-side function. */ |
︙ | ︙ | |||
1176 1177 1178 1179 1180 1181 1182 | ? cache.scopedAlloc.indexOf(state) : cache.scopedAlloc.length-1; if(n<0) toss("Invalid state object for scopedAllocPop()."); if(0===arguments.length) state = cache.scopedAlloc[n]; cache.scopedAlloc.splice(n,1); for(let p; (p = state.pop()); ){ if(target.functionEntry(p)){ | | | 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 | ? cache.scopedAlloc.indexOf(state) : cache.scopedAlloc.length-1; if(n<0) toss("Invalid state object for scopedAllocPop()."); if(0===arguments.length) state = cache.scopedAlloc[n]; cache.scopedAlloc.splice(n,1); for(let p; (p = state.pop()); ){ if(target.functionEntry(p)){ //console.warn("scopedAllocPop() uninstalling function",p); target.uninstallFunction(p); } else target.dealloc(p); } }; /** |
︙ | ︙ | |||
1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 | /** Looks up a WASM-exported function named fname from target.exports. If found, it is called, passed all remaining arguments, and its return value is returned to xCall's caller. If not found, an exception is thrown. This function does no conversion of argument or return types, but see xWrap() and xCallWrapped() for variants which do. As a special case, if passed only 1 argument after the name and that argument in an Array, that array's entries become the function arguments. (This is not an ambiguous case because it's not legal to pass an Array object to a WASM function.) */ target.xCall = function(fname, ...args){ | > > > > | | | 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 | /** Looks up a WASM-exported function named fname from target.exports. If found, it is called, passed all remaining arguments, and its return value is returned to xCall's caller. If not found, an exception is thrown. This function does no conversion of argument or return types, but see xWrap() and xCallWrapped() for variants which do. If the first argument is a function is is assumed to be a WASM-bound function and is used as-is instead of looking up the function via xGet(). As a special case, if passed only 1 argument after the name and that argument in an Array, that array's entries become the function arguments. (This is not an ambiguous case because it's not legal to pass an Array object to a WASM function.) */ target.xCall = function(fname, ...args){ const f = (fname instanceof Function) ? fname : target.xGet(fname); if(!(f instanceof Function)) toss("Exported symbol",fname,"is not a function."); if(f.length!==args.length) __argcMismatch(((f===fname) ? f.name : fname),f.length) /* This is arguably over-pedantic but we want to help clients keep from shooting themselves in the foot when calling C APIs. */; return (2===arguments.length && Array.isArray(arguments[1])) ? f.apply(null, arguments[1]) : f.apply(null, args); }; |
︙ | ︙ | |||
1535 1536 1537 1538 1539 1540 1541 | is solely for debugging and error-reporting purposes. If not provided, an empty string is assumed. - signature: a function signature string compatible with jsFuncToWasm(). - bindScope (string): one of ('transient', 'context', | | | 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 | is solely for debugging and error-reporting purposes. If not provided, an empty string is assumed. - signature: a function signature string compatible with jsFuncToWasm(). - bindScope (string): one of ('transient', 'context', 'singleton', 'permanent'). Bind scopes are: - 'transient': it will convert JS functions to WASM only for the duration of the xWrap()'d function call, using scopedInstallFunction(). Before that call returns, the WASM-side binding will be uninstalled. - 'singleton': holds one function-pointer binding for this |
︙ | ︙ | |||
1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 | constructor(opt) { super(opt); if(xArg.FuncPtrAdapter.warnOnUse){ console.warn('xArg.FuncPtrAdapter is an internal-only API', 'and is not intended to be invoked from', 'client-level code. Invoked with:',opt); } this.signature = opt.signature; if(opt.contextKey instanceof Function){ this.contextKey = opt.contextKey; if(!opt.bindScope) opt.bindScope = 'context'; } this.bindScope = opt.bindScope || toss("FuncPtrAdapter options requires a bindScope (explicit or implied)."); | > | 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 | constructor(opt) { super(opt); if(xArg.FuncPtrAdapter.warnOnUse){ console.warn('xArg.FuncPtrAdapter is an internal-only API', 'and is not intended to be invoked from', 'client-level code. Invoked with:',opt); } this.name = opt.name || "unnamed"; this.signature = opt.signature; if(opt.contextKey instanceof Function){ this.contextKey = opt.contextKey; if(!opt.bindScope) opt.bindScope = 'context'; } this.bindScope = opt.bindScope || toss("FuncPtrAdapter options requires a bindScope (explicit or implied)."); |
︙ | ︙ | |||
1694 1695 1696 1697 1698 1699 1700 | perform any function binding, so this object's bindMode is irrelevant for such cases. See the parent class's convertArg() docs for details on what exactly the 2nd and 3rd arguments are. */ convertArg(v,argv,argIndex){ | | > > | > > > > > > > > > > > > | | 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 | perform any function binding, so this object's bindMode is irrelevant for such cases. See the parent class's convertArg() docs for details on what exactly the 2nd and 3rd arguments are. */ convertArg(v,argv,argIndex){ //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v); let pair = this.singleton; if(!pair && this.isContext){ pair = this.contextMap(this.contextKey(argv,argIndex)); //FuncPtrAdapter.debugOut(this.name, this.signature, "contextKey() =",this.contextKey(argv,argIndex), pair); } if(pair && pair[0]===v) return pair[1]; if(v instanceof Function){ /* Install a WASM binding and return its pointer. */ //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v,pair); if(this.callProxy) v = this.callProxy(v); const fp = __installFunction(v, this.signature, this.isTransient); if(FuncPtrAdapter.debugFuncInstall){ FuncPtrAdapter.debugOut("FuncPtrAdapter installed", this, this.contextKey(argv,argIndex), '@'+fp, v); } if(pair){ /* Replace existing stashed mapping */ if(pair[1]){ if(FuncPtrAdapter.debugFuncInstall){ FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, this.contextKey(argv,argIndex), '@'+pair[1], v); } try{ /* Because the pending native call might rely on the pointer we're replacing, e.g. as is normally the case with sqlite3's xDestroy() methods, we don't immediately uninstall but instead add its pointer to the scopedAlloc stack, which will be cleared when the xWrap() mechanism is done calling the native function. We're relying very much here on xWrap() having pushed an alloc scope. */ cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]); } catch(e){/*ignored*/} } pair[0] = v; pair[1] = fp; } return fp; }else if(target.isPtr(v) || null===v || undefined===v){ //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v,pair); if(pair && pair[1] && pair[1]!==v){ /* uninstall stashed mapping and replace stashed mapping with v. */ if(FuncPtrAdapter.debugFuncInstall){ FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, this.contextKey(argv,argIndex), '@'+pair[1], v); } try{ cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]) } catch(e){/*ignored*/} pair[0] = pair[1] = (v | 0); } return v || 0; }else{ throw new TypeError("Invalid FuncPtrAdapter argument type. "+ "Expecting a function pointer or a "+ |
︙ | ︙ | |||
1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 | const __xArgAdapterCheck = (t)=>xArg.get(t) || toss("Argument adapter not found:",t); const __xResultAdapterCheck = (t)=>xResult.get(t) || toss("Result adapter not found:",t); cache.xWrap.convertArg = (t,...args)=>__xArgAdapterCheck(t)(...args); cache.xWrap.convertArgNoCheck = (t,...args)=>xArg.get(t)(...args); cache.xWrap.convertResult = (t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined)); cache.xWrap.convertResultNoCheck = (t,v)=>(null===t ? v : (t ? xResult.get(t)(v) : undefined)); /** Creates a wrapper for another function which converts the arguments of the wrapper to argument types accepted by the wrapped function, then converts the wrapped function's result to another form | > > > > > > > > > > > > > > > > > > | 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 | const __xArgAdapterCheck = (t)=>xArg.get(t) || toss("Argument adapter not found:",t); const __xResultAdapterCheck = (t)=>xResult.get(t) || toss("Result adapter not found:",t); /** Fetches the xWrap() argument adapter mapped to t, calls it, passing in all remaining arguments, and returns the result. Throws if t is not mapped to an argument converter. */ cache.xWrap.convertArg = (t,...args)=>__xArgAdapterCheck(t)(...args); /** Identical to convertArg() except that it does not perform an is-defined check on the mapping to t before invoking it. */ cache.xWrap.convertArgNoCheck = (t,...args)=>xArg.get(t)(...args); /** Fetches the xWrap() result adapter mapped to t, calls it, passing it v, and returns the result. Throws if t is not mapped to an argument converter. */ cache.xWrap.convertResult = (t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined)); /** Identical to convertResult() except that it does not perform an is-defined check on the mapping to t before invoking it. */ cache.xWrap.convertResultNoCheck = (t,v)=>(null===t ? v : (t ? xResult.get(t)(v) : undefined)); /** Creates a wrapper for another function which converts the arguments of the wrapper to argument types accepted by the wrapped function, then converts the wrapped function's result to another form |
︙ | ︙ |
Changes to ext/wasm/tester1.c-pp.js.
︙ | ︙ | |||
2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 | wasm.pstack.restore(stackPtr); db1.close(); db2.close(); } } })/*session API sanity tests*/ ;/*end of session API group*/; //////////////////////////////////////////////////////////////////////// T.g('OPFS: Origin-Private File System', (sqlite3)=>(sqlite3.capi.sqlite3_vfs_find("opfs") || 'requires "opfs" VFS')) .t({ name: 'OPFS db sanity checks', | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 | wasm.pstack.restore(stackPtr); db1.close(); db2.close(); } } })/*session API sanity tests*/ ;/*end of session API group*/; //////////////////////////////////////////////////////////////////////// T.g('FTS5') .t({ name: "Sanity checks", predicate: (sqlite3)=>sqlite3.fts5 || "Missing sqlite3.fts5 namespace.", test: function(sqlite3){ const db = new sqlite3.oo1.DB(); db.exec([ "create virtual table ft using fts5(a,b);", "insert into ft(a,b) values", "('a1','b1'),", "('a2','b2'),", "('a3','b3');" ]); const fts = sqlite3.fts5; let pApi = fts.fts5_api_from_db(db); T.assert( !!pApi ); let fApi = new fts.fts5_api(pApi); T.assert( fApi.$iVersion >= 2 ); fApi.dispose(); fApi = undefined; let destroyCalled = false; fts.createFunction(db, { name: 'mymatch', xFunction: function(pFtsX, pFtsCx, pCtx, argv){ // Both of these return-value approaches are equivalent: const rv = "MY+"+argv.join(':'); if(0){ return rv; }else{ capi.sqlite3_result_text(pCtx, rv, -1, capi.SQLITE_TRANSIENT); // implicit return of undefined } }, xDestroy: function(){ destroyCalled = true; } }); let list = db.selectValues( "select mymatch(ft,a,b) from ft where b match 'b2'" ); T.assert( 1 === list.length ) .assert( 'MY+a2:b2' === list[0] ); //const fTok = new fts.fts5_tokenizer(); //fTok.installMethods({}); db.close(); T.assert( destroyCalled ); //toss("Testing"); } })/*FTS5*/ //////////////////////////////////////////////////////////////////////// T.g('OPFS: Origin-Private File System', (sqlite3)=>(sqlite3.capi.sqlite3_vfs_find("opfs") || 'requires "opfs" VFS')) .t({ name: 'OPFS db sanity checks', |
︙ | ︙ |