Snapshot of upstream SQLite 3.46.1
[sqlcipher.git] / ext / wasm / api / sqlite3-vtab-helper.c-pp.js
blob7359ea39aa7d0409d07eb126fa082dc95f482dd8
1 /*
2 ** 2022-11-30
3 **
4 ** The author disclaims copyright to this source code. In place of a
5 ** legal notice, here is a blessing:
6 **
7 ** * May you do good and not evil.
8 ** * May you find forgiveness for yourself and forgive others.
9 ** * May you share freely, never taking more than you give.
12 /**
13 This file installs sqlite3.vtab, a namespace of helpers for use in
14 the creation of JavaScript implementations virtual tables.
16 'use strict';
17 globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
18 const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
19 const vtab = Object.create(null);
20 sqlite3.vtab = vtab;
22 const sii = capi.sqlite3_index_info;
23 /**
24 If n is >=0 and less than this.$nConstraint, this function
25 returns either a WASM pointer to the 0-based nth entry of
26 this.$aConstraint (if passed a truthy 2nd argument) or an
27 sqlite3_index_info.sqlite3_index_constraint object wrapping that
28 address (if passed a falsy value or no 2nd argument). Returns a
29 falsy value if n is out of range.
31 sii.prototype.nthConstraint = function(n, asPtr=false){
32 if(n<0 || n>=this.$nConstraint) return false;
33 const ptr = this.$aConstraint + (
34 sii.sqlite3_index_constraint.structInfo.sizeof * n
36 return asPtr ? ptr : new sii.sqlite3_index_constraint(ptr);
39 /**
40 Works identically to nthConstraint() but returns state from
41 this.$aConstraintUsage, so returns an
42 sqlite3_index_info.sqlite3_index_constraint_usage instance
43 if passed no 2nd argument or a falsy 2nd argument.
45 sii.prototype.nthConstraintUsage = function(n, asPtr=false){
46 if(n<0 || n>=this.$nConstraint) return false;
47 const ptr = this.$aConstraintUsage + (
48 sii.sqlite3_index_constraint_usage.structInfo.sizeof * n
50 return asPtr ? ptr : new sii.sqlite3_index_constraint_usage(ptr);
53 /**
54 If n is >=0 and less than this.$nOrderBy, this function
55 returns either a WASM pointer to the 0-based nth entry of
56 this.$aOrderBy (if passed a truthy 2nd argument) or an
57 sqlite3_index_info.sqlite3_index_orderby object wrapping that
58 address (if passed a falsy value or no 2nd argument). Returns a
59 falsy value if n is out of range.
61 sii.prototype.nthOrderBy = function(n, asPtr=false){
62 if(n<0 || n>=this.$nOrderBy) return false;
63 const ptr = this.$aOrderBy + (
64 sii.sqlite3_index_orderby.structInfo.sizeof * n
66 return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr);
69 /**
70 Internal factory function for xVtab and xCursor impls.
72 const __xWrapFactory = function(methodName,StructType){
73 return function(ptr,removeMapping=false){
74 if(0===arguments.length) ptr = new StructType;
75 if(ptr instanceof StructType){
76 //T.assert(!this.has(ptr.pointer));
77 this.set(ptr.pointer, ptr);
78 return ptr;
79 }else if(!wasm.isPtr(ptr)){
80 sqlite3.SQLite3Error.toss("Invalid argument to",methodName+"()");
82 let rc = this.get(ptr);
83 if(removeMapping) this.delete(ptr);
84 return rc;
85 }.bind(new Map);
88 /**
89 A factory function which implements a simple lifetime manager for
90 mappings between C struct pointers and their JS-level wrappers.
91 The first argument must be the logical name of the manager
92 (e.g. 'xVtab' or 'xCursor'), which is only used for error
93 reporting. The second must be the capi.XYZ struct-type value,
94 e.g. capi.sqlite3_vtab or capi.sqlite3_vtab_cursor.
96 Returns an object with 4 methods: create(), get(), unget(), and
97 dispose(), plus a StructType member with the value of the 2nd
98 argument. The methods are documented in the body of this
99 function.
101 const StructPtrMapper = function(name, StructType){
102 const __xWrap = __xWrapFactory(name,StructType);
104 This object houses a small API for managing mappings of (`T*`)
105 to StructType<T> objects, specifically within the lifetime
106 requirements of sqlite3_module methods.
108 return Object.assign(Object.create(null),{
109 /** The StructType object for this object's API. */
110 StructType,
112 Creates a new StructType object, writes its `pointer`
113 value to the given output pointer, and returns that
114 object. Its intended usage depends on StructType:
116 sqlite3_vtab: to be called from sqlite3_module::xConnect()
117 or xCreate() implementations.
119 sqlite3_vtab_cursor: to be called from xOpen().
121 This will throw if allocation of the StructType instance
122 fails or if ppOut is not a pointer-type value.
124 create: (ppOut)=>{
125 const rc = __xWrap();
126 wasm.pokePtr(ppOut, rc.pointer);
127 return rc;
130 Returns the StructType object previously mapped to the
131 given pointer using create(). Its intended usage depends
132 on StructType:
134 sqlite3_vtab: to be called from sqlite3_module methods which
135 take a (sqlite3_vtab*) pointer _except_ for
136 xDestroy()/xDisconnect(), in which case unget() or dispose().
138 sqlite3_vtab_cursor: to be called from any sqlite3_module methods
139 which take a `sqlite3_vtab_cursor*` argument except xClose(),
140 in which case use unget() or dispose().
142 Rule to remember: _never_ call dispose() on an instance
143 returned by this function.
145 get: (pCObj)=>__xWrap(pCObj),
147 Identical to get() but also disconnects the mapping between the
148 given pointer and the returned StructType object, such that
149 future calls to this function or get() with the same pointer
150 will return the undefined value. Its intended usage depends
151 on StructType:
153 sqlite3_vtab: to be called from sqlite3_module::xDisconnect() or
154 xDestroy() implementations or in error handling of a failed
155 xCreate() or xConnect().
157 sqlite3_vtab_cursor: to be called from xClose() or during
158 cleanup in a failed xOpen().
160 Calling this method obligates the caller to call dispose() on
161 the returned object when they're done with it.
163 unget: (pCObj)=>__xWrap(pCObj,true),
165 Works like unget() plus it calls dispose() on the
166 StructType object.
168 dispose: (pCObj)=>{
169 const o = __xWrap(pCObj,true);
170 if(o) o.dispose();
176 A lifetime-management object for mapping `sqlite3_vtab*`
177 instances in sqlite3_module methods to capi.sqlite3_vtab
178 objects.
180 The API docs are in the API-internal StructPtrMapper().
182 vtab.xVtab = StructPtrMapper('xVtab', capi.sqlite3_vtab);
185 A lifetime-management object for mapping `sqlite3_vtab_cursor*`
186 instances in sqlite3_module methods to capi.sqlite3_vtab_cursor
187 objects.
189 The API docs are in the API-internal StructPtrMapper().
191 vtab.xCursor = StructPtrMapper('xCursor', capi.sqlite3_vtab_cursor);
194 Convenience form of creating an sqlite3_index_info wrapper,
195 intended for use in xBestIndex implementations. Note that the
196 caller is expected to call dispose() on the returned object
197 before returning. Though not _strictly_ required, as that object
198 does not own the pIdxInfo memory, it is nonetheless good form.
200 vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo);
203 Given an sqlite3_module method name and error object, this
204 function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof
205 sqlite3.WasmAllocError), else it returns its second argument. Its
206 intended usage is in the methods of a sqlite3_vfs or
207 sqlite3_module:
210 try{
211 let rc = ...
212 return rc;
213 }catch(e){
214 return sqlite3.vtab.xError(
215 'xColumn', e, sqlite3.capi.SQLITE_XYZ);
216 // where SQLITE_XYZ is some call-appropriate result code.
220 If no 3rd argument is provided, its default depends on
221 the error type:
223 - An sqlite3.WasmAllocError always resolves to capi.SQLITE_NOMEM.
225 - If err is an SQLite3Error then its `resultCode` property
226 is used.
228 - If all else fails, capi.SQLITE_ERROR is used.
230 If xError.errorReporter is a function, it is called in
231 order to report the error, else the error is not reported.
232 If that function throws, that exception is ignored.
234 vtab.xError = function f(methodName, err, defaultRc){
235 if(f.errorReporter instanceof Function){
236 try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);}
237 catch(e){/*ignored*/}
239 let rc;
240 if(err instanceof sqlite3.WasmAllocError) rc = capi.SQLITE_NOMEM;
241 else if(arguments.length>2) rc = defaultRc;
242 else if(err instanceof sqlite3.SQLite3Error) rc = err.resultCode;
243 return rc || capi.SQLITE_ERROR;
245 vtab.xError.errorReporter = 1 ? console.error.bind(console) : false;
248 A helper for sqlite3_vtab::xRowid() and xUpdate()
249 implementations. It must be passed the final argument to one of
250 those methods (an output pointer to an int64 row ID) and the
251 value to store at the output pointer's address. Returns the same
252 as wasm.poke() and will throw if the 1st or 2nd arguments
253 are invalid for that function.
255 Example xRowid impl:
258 const xRowid = (pCursor, ppRowid64)=>{
259 const c = vtab.xCursor(pCursor);
260 vtab.xRowid(ppRowid64, c.myRowId);
261 return 0;
265 vtab.xRowid = (ppRowid64, value)=>wasm.poke(ppRowid64, value, 'i64');
268 A helper to initialize and set up an sqlite3_module object for
269 later installation into individual databases using
270 sqlite3_create_module(). Requires an object with the following
271 properties:
273 - `methods`: an object containing a mapping of properties with
274 the C-side names of the sqlite3_module methods, e.g. xCreate,
275 xBestIndex, etc., to JS implementations for those functions.
276 Certain special-case handling is performed, as described below.
278 - `catchExceptions` (default=false): if truthy, the given methods
279 are not mapped as-is, but are instead wrapped inside wrappers
280 which translate exceptions into result codes of SQLITE_ERROR or
281 SQLITE_NOMEM, depending on whether the exception is an
282 sqlite3.WasmAllocError. In the case of the xConnect and xCreate
283 methods, the exception handler also sets the output error
284 string to the exception's error string.
286 - OPTIONAL `struct`: a sqlite3.capi.sqlite3_module() instance. If
287 not set, one will be created automatically. If the current
288 "this" is-a sqlite3_module then it is unconditionally used in
289 place of `struct`.
291 - OPTIONAL `iVersion`: if set, it must be an integer value and it
292 gets assigned to the `$iVersion` member of the struct object.
293 If it's _not_ set, and the passed-in `struct` object's `$iVersion`
294 is 0 (the default) then this function attempts to define a value
295 for that property based on the list of methods it has.
297 If `catchExceptions` is false, it is up to the client to ensure
298 that no exceptions escape the methods, as doing so would move
299 them through the C API, leading to undefined
300 behavior. (vtab.xError() is intended to assist in reporting
301 such exceptions.)
303 Certain methods may refer to the same implementation. To simplify
304 the definition of such methods:
306 - If `methods.xConnect` is `true` then the value of
307 `methods.xCreate` is used in its place, and vice versa. sqlite
308 treats xConnect/xCreate functions specially if they are exactly
309 the same function (same pointer value).
311 - If `methods.xDisconnect` is true then the value of
312 `methods.xDestroy` is used in its place, and vice versa.
314 This is to facilitate creation of those methods inline in the
315 passed-in object without requiring the client to explicitly get a
316 reference to one of them in order to assign it to the other
317 one.
319 The `catchExceptions`-installed handlers will account for
320 identical references to the above functions and will install the
321 same wrapper function for both.
323 The given methods are expected to return integer values, as
324 expected by the C API. If `catchExceptions` is truthy, the return
325 value of the wrapped function will be used as-is and will be
326 translated to 0 if the function returns a falsy value (e.g. if it
327 does not have an explicit return). If `catchExceptions` is _not_
328 active, the method implementations must explicitly return integer
329 values.
331 Throws on error. On success, returns the sqlite3_module object
332 (`this` or `opt.struct` or a new sqlite3_module instance,
333 depending on how it's called).
335 vtab.setupModule = function(opt){
336 let createdMod = false;
337 const mod = (this instanceof capi.sqlite3_module)
338 ? this : (opt.struct || (createdMod = new capi.sqlite3_module()));
339 try{
340 const methods = opt.methods || toss("Missing 'methods' object.");
341 for(const e of Object.entries({
342 // -----^ ==> [k,v] triggers a broken code transformation in
343 // some versions of the emsdk toolchain.
344 xConnect: 'xCreate', xDisconnect: 'xDestroy'
345 })){
346 // Remap X=true to X=Y for certain X/Y combinations
347 const k = e[0], v = e[1];
348 if(true === methods[k]) methods[k] = methods[v];
349 else if(true === methods[v]) methods[v] = methods[k];
351 if(opt.catchExceptions){
352 const fwrap = function(methodName, func){
353 if(['xConnect','xCreate'].indexOf(methodName) >= 0){
354 return function(pDb, pAux, argc, argv, ppVtab, pzErr){
355 try{return func(...arguments) || 0}
356 catch(e){
357 if(!(e instanceof sqlite3.WasmAllocError)){
358 wasm.dealloc(wasm.peekPtr(pzErr));
359 wasm.pokePtr(pzErr, wasm.allocCString(e.message));
361 return vtab.xError(methodName, e);
364 }else{
365 return function(...args){
366 try{return func(...args) || 0}
367 catch(e){
368 return vtab.xError(methodName, e);
373 const mnames = [
374 'xCreate', 'xConnect', 'xBestIndex', 'xDisconnect',
375 'xDestroy', 'xOpen', 'xClose', 'xFilter', 'xNext',
376 'xEof', 'xColumn', 'xRowid', 'xUpdate',
377 'xBegin', 'xSync', 'xCommit', 'xRollback',
378 'xFindFunction', 'xRename', 'xSavepoint', 'xRelease',
379 'xRollbackTo', 'xShadowName'
381 const remethods = Object.create(null);
382 for(const k of mnames){
383 const m = methods[k];
384 if(!(m instanceof Function)) continue;
385 else if('xConnect'===k && methods.xCreate===m){
386 remethods[k] = methods.xCreate;
387 }else if('xCreate'===k && methods.xConnect===m){
388 remethods[k] = methods.xConnect;
389 }else{
390 remethods[k] = fwrap(k, m);
393 mod.installMethods(remethods, false);
394 }else{
395 // No automatic exception handling. Trust the client
396 // to not throw.
397 mod.installMethods(
398 methods, !!opt.applyArgcCheck/*undocumented option*/
401 if(0===mod.$iVersion){
402 let v;
403 if('number'===typeof opt.iVersion) v = opt.iVersion;
404 else if(mod.$xShadowName) v = 3;
405 else if(mod.$xSavePoint || mod.$xRelease || mod.$xRollbackTo) v = 2;
406 else v = 1;
407 mod.$iVersion = v;
409 }catch(e){
410 if(createdMod) createdMod.dispose();
411 throw e;
413 return mod;
414 }/*setupModule()*/;
417 Equivalent to calling vtab.setupModule() with this sqlite3_module
418 object as the call's `this`.
420 capi.sqlite3_module.prototype.setupModule = function(opt){
421 return vtab.setupModule.call(this, opt);
423 }/*sqlite3ApiBootstrap.initializers.push()*/);