sqlite/ext/wasm/api/sqlite3-api-prologue.js
stephan 81439a07f0 When calculating kvvfs sessionStorage/localStorage size, multiply it by 2 to account for JS using 16-bit characters.
FossilOrigin-Name: 52d1b185b9f6cee1eb3dec436f47e0f52e4621a127abfad8c27f92fd78147889
2022-09-16 12:49:45 +00:00

919 lines
37 KiB
JavaScript

/*
2022-05-22
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 is intended to be combined at build-time with other
related code, most notably a header and footer which wraps this whole
file into an Emscripten Module.postRun() handler which has a parameter
named "Module" (the Emscripten Module object). The exact requirements,
conventions, and build process are very much under construction and
will be (re)documented once they've stopped fluctuating so much.
Specific goals of this project:
- Except where noted in the non-goals, provide a more-or-less
feature-complete wrapper to the sqlite3 C API, insofar as WASM
feature parity with C allows for. In fact, provide at least 3
APIs...
1) Bind a low-level sqlite3 API which is as close to the native
one as feasible in terms of usage.
2) A higher-level API, more akin to sql.js and node.js-style
implementations. This one speaks directly to the low-level
API. This API must be used from the same thread as the
low-level API.
3) A second higher-level API which speaks to the previous APIs via
worker messages. This one is intended for use in the main
thread, with the lower-level APIs installed in a Worker thread,
and talking to them via Worker messages. Because Workers are
asynchronouns and have only a single message channel, some
acrobatics are needed here to feed async work results back to
the client (as we cannot simply pass around callbacks between
the main and Worker threads).
- Insofar as possible, support client-side storage using JS
filesystem APIs. As of this writing, such things are still very
much under development.
Specific non-goals of this project:
- As WASM is a web-centric technology and UTF-8 is the King of
Encodings in that realm, there are no currently plans to support
the UTF16-related sqlite3 APIs. They would add a complication to
the bindings for no appreciable benefit. Though web-related
implementation details take priority, and the JavaScript
components of the API specifically focus on browser clients, the
lower-level WASM module "should" work in non-web WASM
environments.
- Supporting old or niche-market platforms. WASM is built for a
modern web and requires modern platforms.
- Though scalar User-Defined Functions (UDFs) may be created in
JavaScript, there are currently no plans to add support for
aggregate and window functions.
Attribution:
This project is endebted to the work of sql.js:
https://github.com/sql-js/sql.js
sql.js was an essential stepping stone in this code's development as
it demonstrated how to handle some of the WASM-related voodoo (like
handling pointers-to-pointers and adding JS implementations of
C-bound callback functions). These APIs have a considerably
different shape than sql.js's, however.
*/
/**
sqlite3ApiBootstrap() is the only global symbol persistently
exposed by this API. It is intended to be called one time at the
end of the API amalgamation process, passed configuration details
for the current environment, and then optionally be removed from
the global object using `delete self.sqlite3ApiBootstrap`.
This function expects a configuration object, intended to abstract
away details specific to any given WASM environment, primarily so
that it can be used without any _direct_ dependency on
Emscripten. (Note the default values for the config object!) The
config object is only honored the first time this is
called. Subsequent calls ignore the argument and return the same
(configured) object which gets initialized by the first call.
The config object properties include:
- `Module`[^1]: Emscripten-style module object. Currently only required
by certain test code and is _not_ part of the public interface.
(TODO: rename this to EmscriptenModule to be more explicit.)
- `exports`[^1]: the "exports" object for the current WASM
environment. In an Emscripten build, this should be set to
`Module['asm']`.
- `memory`[^1]: optional WebAssembly.Memory object, defaulting to
`exports.memory`. In Emscripten environments this should be set
to `Module.wasmMemory` if the build uses `-sIMPORT_MEMORY`, or be
left undefined/falsy to default to `exports.memory` when using
WASM-exported memory.
- `bigIntEnabled`: true if BigInt support is enabled. Defaults to
true if self.BigInt64Array is available, else false. Some APIs
will throw exceptions if called without BigInt support, as BigInt
is required for marshalling C-side int64 into and out of JS.
- `allocExportName`: the name of the function, in `exports`, of the
`malloc(3)`-compatible routine for the WASM environment. Defaults
to `"malloc"`.
- `deallocExportName`: the name of the function, in `exports`, of
the `free(3)`-compatible routine for the WASM
environment. Defaults to `"free"`.
- `persistentDirName`[^1]: if the environment supports persistent storage, this
directory names the "mount point" for that directory. It must be prefixed
by `/` and may currently contain only a single directory-name part. Using
the root directory name is not supported by any current persistent backend.
[^1] = This property may optionally be a function, in which case this
function re-assigns it to the value returned from that function,
enabling delayed evaluation.
*/
'use strict';
self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
apiConfig = (self.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig)
){
if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */
console.warn("sqlite3ApiBootstrap() called multiple times.",
"Config and external initializers are ignored on calls after the first.");
return sqlite3ApiBootstrap.sqlite3;
}
apiConfig = apiConfig || {};
const config = Object.create(null);
{
const configDefaults = {
Module: undefined/*needed for some test code, not part of the public API*/,
exports: undefined,
memory: undefined,
bigIntEnabled: !!self.BigInt64Array,
allocExportName: 'malloc',
deallocExportName: 'free',
persistentDirName: '/persistent'
};
Object.keys(configDefaults).forEach(function(k){
config[k] = Object.getOwnPropertyDescriptor(apiConfig, k)
? apiConfig[k] : configDefaults[k];
});
// Copy over any properties apiConfig defines but configDefaults does not...
Object.keys(apiConfig).forEach(function(k){
if(!Object.getOwnPropertyDescriptor(config, k)){
config[k] = apiConfig[k];
}
});
}
[
// If any of these config options are functions, replace them with
// the result of calling that function...
'Module', 'exports', 'memory', 'persistentDirName'
].forEach((k)=>{
if('function' === typeof config[k]){
config[k] = config[k]();
}
});
/** Throws a new Error, the message of which is the concatenation
all args with a space between each. */
const toss = (...args)=>{throw new Error(args.join(' '))};
if(config.persistentDirName && !/^\/[^/]+$/.test(config.persistentDirName)){
toss("config.persistentDirName must be falsy or in the form '/dir-name'.");
}
/**
Returns true if n is a 32-bit (signed) integer, else
false. This is used for determining when we need to switch to
double-type DB operations for integer values in order to keep
more precision.
*/
const isInt32 = function(n){
return ('bigint'!==typeof n /*TypeError: can't convert BigInt to number*/)
&& !!(n===(n|0) && n<=2147483647 && n>=-2147483648);
};
/** Returns v if v appears to be a TypedArray, else false. */
const isTypedArray = (v)=>{
return (v && v.constructor && isInt32(v.constructor.BYTES_PER_ELEMENT)) ? v : false;
};
/**
Returns true if v appears to be one of our bind()-able
TypedArray types: Uint8Array or Int8Array. Support for
TypedArrays with element sizes >1 is TODO.
*/
const isBindableTypedArray = (v)=>{
return v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT);
};
/**
Returns true if v appears to be one of the TypedArray types
which is legal for holding SQL code (as opposed to binary blobs).
Currently this is the same as isBindableTypedArray() but it
seems likely that we'll eventually want to add Uint32Array
and friends to the isBindableTypedArray() list but not to the
isSQLableTypedArray() list.
*/
const isSQLableTypedArray = (v)=>{
return v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT);
};
/** Returns true if isBindableTypedArray(v) does, else throws with a message
that v is not a supported TypedArray value. */
const affirmBindableTypedArray = (v)=>{
return isBindableTypedArray(v)
|| toss("Value is not of a supported TypedArray type.");
};
const utf8Decoder = new TextDecoder('utf-8');
/** Internal helper to use in operations which need to distinguish
between SharedArrayBuffer heap memory and non-shared heap. */
const __SAB = ('undefined'===typeof SharedArrayBuffer)
? function(){} : SharedArrayBuffer;
const typedArrayToString = function(arrayBuffer, begin, end){
return utf8Decoder.decode(
(arrayBuffer.buffer instanceof __SAB)
? arrayBuffer.slice(begin, end)
: arrayBuffer.subarray(begin, end)
);
};
/**
An Error subclass specifically for reporting Wasm-level malloc()
failure and enabling clients to unambiguously identify such
exceptions.
*/
class WasmAllocError extends Error {
constructor(...args){
super(...args);
this.name = 'WasmAllocError';
}
};
/**
The main sqlite3 binding API gets installed into this object,
mimicking the C API as closely as we can. The numerous members
names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as
possible, identically to the C-native counterparts, as documented at:
https://www.sqlite.org/c3ref/intro.html
A very few exceptions require an additional level of proxy
function or may otherwise require special attention in the WASM
environment, and all such cases are document here. Those not
documented here are installed as 1-to-1 proxies for their C-side
counterparts.
*/
const capi = {
/**
When using sqlite3_open_v2() it is important to keep the following
in mind:
https://www.sqlite.org/c3ref/open.html
- The flags for use with its 3rd argument are installed in this
object using the C-cide names, e.g. SQLITE_OPEN_CREATE.
- If the combination of flags passed to it are invalid,
behavior is undefined. Thus is is never okay to call this
with fewer than 3 arguments, as JS will default the
missing arguments to `undefined`, which will result in a
flag value of 0. Most of the available SQLITE_OPEN_xxx
flags are meaningless in the WASM build, e.g. the mutext-
and cache-related flags, but they are retained in this
API for consistency's sake.
- The final argument to this function specifies the VFS to
use, which is largely (but not entirely!) meaningless in
the WASM environment. It should always be null or
undefined, and it is safe to elide that argument when
calling this function.
*/
sqlite3_open_v2: function(filename,dbPtrPtr,flags,vfsStr){}/*installed later*/,
/**
The sqlite3_prepare_v3() binding handles two different uses
with differing JS/WASM semantics:
1) sqlite3_prepare_v3(pDb, sqlString, -1, prepFlags, ppStmt [, null])
2) sqlite3_prepare_v3(pDb, sqlPointer, sqlByteLen, prepFlags, ppStmt, sqlPointerToPointer)
Note that the SQL length argument (the 3rd argument) must, for
usage (1), always be negative because it must be a byte length
and that value is expensive to calculate from JS (where only
the character length of strings is readily available). It is
retained in this API's interface for code/documentation
compatibility reasons but is currently _always_ ignored. With
usage (2), the 3rd argument is used as-is but is is still
critical that the C-style input string (2nd argument) be
terminated with a 0 byte.
In usage (1), the 2nd argument must be of type string,
Uint8Array, or Int8Array (either of which is assumed to
hold SQL). If it is, this function assumes case (1) and
calls the underyling C function with the equivalent of:
(pDb, sqlAsString, -1, prepFlags, ppStmt, null)
The pzTail argument is ignored in this case because its result
is meaningless when a string-type value is passed through
(because the string goes through another level of internal
conversion for WASM's sake and the result pointer would refer
to that transient conversion's memory, not the passed-in
string).
If the sql argument is not a string, it must be a _pointer_ to
a NUL-terminated string which was allocated in the WASM memory
(e.g. using cwapi.wasm.alloc() or equivalent). In that case,
the final argument may be 0/null/undefined or must be a pointer
to which the "tail" of the compiled SQL is written, as
documented for the C-side sqlite3_prepare_v3(). In case (2),
the underlying C function is called with the equivalent of:
(pDb, sqlAsPointer, (sqlByteLen||-1), prepFlags, ppStmt, pzTail)
It returns its result and compiled statement as documented in
the C API. Fetching the output pointers (5th and 6th
parameters) requires using capi.wasm.getMemValue() (or
equivalent) and the pzTail will point to an address relative to
the sqlAsPointer value.
If passed an invalid 2nd argument type, this function will
return SQLITE_MISUSE but will unfortunately be able to return
any additional error information because we have no way to set
the db's error state such that this function could return a
non-0 integer and the client could call sqlite3_errcode() or
sqlite3_errmsg() to fetch it. See the RFE at:
https://sqlite.org/forum/forumpost/f9eb79b11aefd4fc81d
The alternative would be to throw an exception for that case,
but that would be in strong constrast to the rest of the
C-level API and seems likely to cause more confusion.
Side-note: in the C API the function does not fail if provided
an empty string but its result output pointer will be NULL.
*/
sqlite3_prepare_v3: function(dbPtr, sql, sqlByteLen, prepFlags,
stmtPtrPtr, strPtrPtr){}/*installed later*/,
/**
Equivalent to calling sqlite3_prapare_v3() with 0 as its 4th argument.
*/
sqlite3_prepare_v2: function(dbPtr, sql, sqlByteLen, stmtPtrPtr,
strPtrPtr){}/*installed later*/,
/**
Various internal-use utilities are added here as needed. They
are bound to an object only so that we have access to them in
the differently-scoped steps of the API bootstrapping
process. At the end of the API setup process, this object gets
removed.
*/
util:{
isInt32, isTypedArray, isBindableTypedArray, isSQLableTypedArray,
affirmBindableTypedArray, typedArrayToString
},
/**
Holds state which are specific to the WASM-related
infrastructure and glue code. It is not expected that client
code will normally need these, but they're exposed here in case
it does. These APIs are _not_ to be considered an
official/stable part of the sqlite3 WASM API. They may change
as the developers' experience suggests appropriate changes.
Note that a number of members of this object are injected
dynamically after the api object is fully constructed, so
not all are documented inline here.
*/
wasm: {
//^^^ TODO?: move wasm from sqlite3.capi.wasm to sqlite3.wasm
/**
Emscripten APIs have a deep-seated assumption that all pointers
are 32 bits. We'll remain optimistic that that won't always be
the case and will use this constant in places where we might
otherwise use a hard-coded 4.
*/
ptrSizeof: config.wasmPtrSizeof || 4,
/**
The WASM IR (Intermediate Representation) value for
pointer-type values. It MUST refer to a value type of the
size described by this.ptrSizeof _or_ it may be any value
which ends in '*', which Emscripten's glue code internally
translates to i32.
*/
ptrIR: config.wasmPtrIR || "i32",
/**
True if BigInt support was enabled via (e.g.) the
Emscripten -sWASM_BIGINT flag, else false. When
enabled, certain 64-bit sqlite3 APIs are enabled which
are not otherwise enabled due to JS/WASM int64
impedence mismatches.
*/
bigIntEnabled: !!config.bigIntEnabled,
/**
The symbols exported by the WASM environment.
*/
exports: config.exports
|| toss("Missing API config.exports (WASM module exports)."),
/**
When Emscripten compiles with `-sIMPORT_MEMORY`, it
initalizes the heap and imports it into wasm, as opposed to
the other way around. In this case, the memory is not
available via this.exports.memory.
*/
memory: config.memory || config.exports['memory']
|| toss("API config object requires a WebAssembly.Memory object",
"in either config.exports.memory (exported)",
"or config.memory (imported)."),
/**
The API's one single point of access to the WASM-side memory
allocator. Works like malloc(3) (and is likely bound to
malloc()) but throws an WasmAllocError if allocation fails. It is
important that any code which might pass through the sqlite3 C
API NOT throw and must instead return SQLITE_NOMEM (or
equivalent, depending on the context).
That said, very few cases in the API can result in
client-defined functions propagating exceptions via the C-style
API. Most notably, this applies ot User-defined SQL Functions
(UDFs) registered via sqlite3_create_function_v2(). For that
specific case it is recommended that all UDF creation be
funneled through a utility function and that a wrapper function
be added around the UDF which catches any exception and sets
the error state to OOM. (The overall complexity of registering
UDFs essentially requires a helper for doing so!)
*/
alloc: undefined/*installed later*/,
/**
The API's one single point of access to the WASM-side memory
deallocator. Works like free(3) (and is likely bound to
free()).
*/
dealloc: undefined/*installed later*/
/* Many more wasm-related APIs get installed later on. */
}/*wasm*/
}/*capi*/;
/**
capi.wasm.alloc()'s srcTypedArray.byteLength bytes,
populates them with the values from the source
TypedArray, and returns the pointer to that memory. The
returned pointer must eventually be passed to
capi.wasm.dealloc() to clean it up.
As a special case, to avoid further special cases where
this is used, if srcTypedArray.byteLength is 0, it
allocates a single byte and sets it to the value
0. Even in such cases, calls must behave as if the
allocated memory has exactly srcTypedArray.byteLength
bytes.
ACHTUNG: this currently only works for Uint8Array and
Int8Array types and will throw if srcTypedArray is of
any other type.
*/
capi.wasm.allocFromTypedArray = function(srcTypedArray){
affirmBindableTypedArray(srcTypedArray);
const pRet = this.alloc(srcTypedArray.byteLength || 1);
this.heapForSize(srcTypedArray.constructor).set(srcTypedArray.byteLength ? srcTypedArray : [0], pRet);
return pRet;
}.bind(capi.wasm);
const keyAlloc = config.allocExportName || 'malloc',
keyDealloc = config.deallocExportName || 'free';
for(const key of [keyAlloc, keyDealloc]){
const f = capi.wasm.exports[key];
if(!(f instanceof Function)) toss("Missing required exports[",key,"] function.");
}
capi.wasm.alloc = function(n){
const m = this.exports[keyAlloc](n);
if(!m) throw new WasmAllocError("Failed to allocate "+n+" bytes.");
return m;
}.bind(capi.wasm)
capi.wasm.dealloc = (m)=>capi.wasm.exports[keyDealloc](m);
/**
Reports info about compile-time options using
sqlite_compileoption_get() and sqlite3_compileoption_used(). It
has several distinct uses:
If optName is an array then it is expected to be a list of
compilation options and this function returns an object
which maps each such option to true or false, indicating
whether or not the given option was included in this
build. That object is returned.
If optName is an object, its keys are expected to be compilation
options and this function sets each entry to true or false,
indicating whether the compilation option was used or not. That
object is returned.
If passed no arguments then it returns an object mapping
all known compilation options to their compile-time values,
or boolean true if they are defined with no value. This
result, which is relatively expensive to compute, is cached
and returned for future no-argument calls.
In all other cases it returns true if the given option was
active when when compiling the sqlite3 module, else false.
Compile-time option names may optionally include their
"SQLITE_" prefix. When it returns an object of all options,
the prefix is elided.
*/
capi.wasm.compileOptionUsed = function f(optName){
if(!arguments.length){
if(f._result) return f._result;
else if(!f._opt){
f._rx = /^([^=]+)=(.+)/;
f._rxInt = /^-?\d+$/;
f._opt = function(opt, rv){
const m = f._rx.exec(opt);
rv[0] = (m ? m[1] : opt);
rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true;
};
}
const rc = {}, ov = [0,0];
let i = 0, k;
while((k = capi.sqlite3_compileoption_get(i++))){
f._opt(k,ov);
rc[ov[0]] = ov[1];
}
return f._result = rc;
}else if(Array.isArray(optName)){
const rc = {};
optName.forEach((v)=>{
rc[v] = capi.sqlite3_compileoption_used(v);
});
return rc;
}else if('object' === typeof optName){
Object.keys(optName).forEach((k)=> {
optName[k] = capi.sqlite3_compileoption_used(k);
});
return optName;
}
return (
'string'===typeof optName
) ? !!capi.sqlite3_compileoption_used(optName) : false;
}/*compileOptionUsed()*/;
/**
Signatures for the WASM-exported C-side functions. Each entry
is an array with 2+ elements:
[ "c-side name",
"result type" (capi.wasm.xWrap() syntax),
[arg types in xWrap() syntax]
// ^^^ this needn't strictly be an array: it can be subsequent
// elements instead: [x,y,z] is equivalent to x,y,z
]
Note that support for the API-specific data types in the
result/argument type strings gets plugged in at a later phase in
the API initialization process.
*/
capi.wasm.bindingSignatures = [
// Please keep these sorted by function name!
["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*"],
["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"],
["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"],
["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"],
["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"],
["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"],
["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int"],
["sqlite3_close_v2", "int", "sqlite3*"],
["sqlite3_changes", "int", "sqlite3*"],
["sqlite3_clear_bindings","int", "sqlite3_stmt*"],
["sqlite3_column_blob","*", "sqlite3_stmt*", "int"],
["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"],
["sqlite3_column_count", "int", "sqlite3_stmt*"],
["sqlite3_column_double","f64", "sqlite3_stmt*", "int"],
["sqlite3_column_int","int", "sqlite3_stmt*", "int"],
["sqlite3_column_name","string", "sqlite3_stmt*", "int"],
["sqlite3_column_text","string", "sqlite3_stmt*", "int"],
["sqlite3_column_type","int", "sqlite3_stmt*", "int"],
["sqlite3_compileoption_get", "string", "int"],
["sqlite3_compileoption_used", "int", "string"],
["sqlite3_create_function_v2", "int",
"sqlite3*", "string", "int", "int", "*", "*", "*", "*", "*"],
["sqlite3_data_count", "int", "sqlite3_stmt*"],
["sqlite3_db_filename", "string", "sqlite3*", "string"],
["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"],
["sqlite3_db_name", "string", "sqlite3*", "int"],
["sqlite3_errmsg", "string", "sqlite3*"],
["sqlite3_error_offset", "int", "sqlite3*"],
["sqlite3_errstr", "string", "int"],
//["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**"],
// ^^^ TODO: we need a wrapper to support passing a function pointer or a function
// for the callback.
["sqlite3_expanded_sql", "string", "sqlite3_stmt*"],
["sqlite3_extended_errcode", "int", "sqlite3*"],
["sqlite3_extended_result_codes", "int", "sqlite3*", "int"],
["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"],
["sqlite3_finalize", "int", "sqlite3_stmt*"],
["sqlite3_initialize", undefined],
["sqlite3_interrupt", undefined, "sqlite3*"
/* ^^^ we cannot actually currently support this because JS is
single-threaded and we don't have a portable way to access a DB
from 2 SharedWorkers concurrently. */],
["sqlite3_libversion", "string"],
["sqlite3_libversion_number", "int"],
["sqlite3_open", "int", "string", "*"],
["sqlite3_open_v2", "int", "string", "*", "int", "string"],
/* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled
separately due to us requiring two different sets of semantics
for those, depending on how their SQL argument is provided. */
["sqlite3_reset", "int", "sqlite3_stmt*"],
["sqlite3_result_blob",undefined, "*", "*", "int", "*"],
["sqlite3_result_double",undefined, "*", "f64"],
["sqlite3_result_error",undefined, "*", "string", "int"],
["sqlite3_result_error_code", undefined, "*", "int"],
["sqlite3_result_error_nomem", undefined, "*"],
["sqlite3_result_error_toobig", undefined, "*"],
["sqlite3_result_int",undefined, "*", "int"],
["sqlite3_result_null",undefined, "*"],
["sqlite3_result_text",undefined, "*", "string", "int", "*"],
["sqlite3_sourceid", "string"],
["sqlite3_sql", "string", "sqlite3_stmt*"],
["sqlite3_step", "int", "sqlite3_stmt*"],
["sqlite3_strglob", "int", "string","string"],
["sqlite3_strlike", "int", "string","string","int"],
["sqlite3_total_changes", "int", "sqlite3*"],
["sqlite3_value_blob", "*", "*"],
["sqlite3_value_bytes","int", "*"],
["sqlite3_value_double","f64", "*"],
["sqlite3_value_text", "string", "*"],
["sqlite3_value_type", "int", "*"],
["sqlite3_vfs_find", "*", "string"],
["sqlite3_vfs_register", "int", "*", "int"]
]/*capi.wasm.bindingSignatures*/;
if(false && capi.wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){
/* ^^^ "the problem" is that this is an option feature and the
build-time function-export list does not currently take
optional features into account. */
capi.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
dummy impls, depending on the capabilities of the environment.
*/
capi.wasm.bindingSignatures.int64 = [
["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]],
["sqlite3_changes64","i64", ["sqlite3*"]],
["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]],
["sqlite3_total_changes64", "i64", ["sqlite3*"]]
];
/**
Functions which are intended solely for API-internal use by the
WASM components, not client code. These get installed into
capi.wasm.
*/
capi.wasm.bindingSignatures.wasm = [
["sqlite3_wasm_vfs_unlink", "int", "string"]
];
/** State for sqlite3_web_persistent_dir(). */
let __persistentDir;
/**
An experiment. Do not use.
If the wasm environment has a persistent storage directory,
its path is returned by this function. If it does not then
it returns "" (noting that "" is a falsy value).
The first time this is called, this function inspects the current
environment to determine whether persistence filesystem support
is available and, if it is, enables it (if needed).
TODOs and caveats:
- If persistent storage is available at the root of the virtual
filesystem, this interface cannot currently distinguish that
from the lack of persistence. That case cannot currently (with
WASMFS/OPFS) happen, but it is conceivably possible in future
environments or non-browser runtimes (none of which are yet
supported targets).
*/
capi.sqlite3_web_persistent_dir = function(){
if(undefined !== __persistentDir) return __persistentDir;
// If we have no OPFS, there is no persistent dir
const pdir = config.persistentDirName;
if(!pdir
|| !self.FileSystemHandle
|| !self.FileSystemDirectoryHandle
|| !self.FileSystemFileHandle){
return __persistentDir = "";
}
try{
if(pdir && 0===capi.wasm.xCallWrapped(
'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir
)){
/** OPFS does not support locking and will trigger errors if
we try to lock. We don't _really_ want to
_unconditionally_ install a non-locking sqlite3 VFS as the
default, but we do so here for simplicy's sake for the
time being. That said: locking is a no-op on all of the
current WASM storage, so this isn't (currently) as bad as
it may initially seem. */
const pVfs = sqlite3.capi.sqlite3_vfs_find("unix-none");
if(pVfs){
capi.sqlite3_vfs_register(pVfs,1);
console.warn("Installed 'unix-none' as the default sqlite3 VFS.");
}
return __persistentDir = pdir;
}else{
return __persistentDir = "";
}
}catch(e){
// sqlite3_wasm_init_wasmfs() is not available
return __persistentDir = "";
}
};
/**
Returns true if sqlite3.capi.sqlite3_web_persistent_dir() is a
non-empty string and the given name starts with (that string +
'/'), else returns false.
Potential (but arguable) TODO: return true if the name is one of
(":localStorage:", "local", ":sessionStorage:", "session") and
kvvfs is available.
*/
capi.sqlite3_web_filename_is_persistent = function(name){
const p = capi.sqlite3_web_persistent_dir();
return (p && name) ? name.startsWith(p+'/') : false;
};
if(0===capi.wasm.exports.sqlite3_vfs_find(0)){
/* Assume that sqlite3_initialize() has not yet been called.
This will be the case in an SQLITE_OS_KV build. */
capi.wasm.exports.sqlite3_initialize();
}
if( self.window===self ){
/* Features specific to the main window thread... */
/**
Internal helper for sqlite3_web_kvvfs_clear() and friends.
Its argument should be one of ('local','session','').
*/
const __kvvfsInfo = function(which){
const rc = Object.create(null);
rc.prefix = 'kvvfs-'+which;
rc.stores = [];
if('session'===which || ''===which) rc.stores.push(self.sessionStorage);
if('local'===which || ''===which) rc.stores.push(self.localStorage);
return rc;
};
/**
Clears all storage used by the kvvfs DB backend, deleting any
DB(s) stored there. Its argument must be either 'session',
'local', or ''. In the first two cases, only sessionStorage
resp. localStorage is cleared. If it's an empty string (the
default) then both are cleared. Only storage keys which match
the pattern used by kvvfs are cleared: any other client-side
data are retained.
This function is only available in the main window thread.
Returns the number of entries cleared.
*/
capi.sqlite3_web_kvvfs_clear = function(which=''){
let rc = 0;
const kvinfo = __kvvfsInfo(which);
kvinfo.stores.forEach((s)=>{
const toRm = [] /* keys to remove */;
let i;
for( i = 0; i < s.length; ++i ){
const k = s.key(i);
if(k.startsWith(kvinfo.prefix)) toRm.push(k);
}
toRm.forEach((kk)=>s.removeItem(kk));
rc += toRm.length;
});
return rc;
};
/**
This routine guesses the approximate amount of
window.localStorage and/or window.sessionStorage in use by the
kvvfs database backend. Its argument must be one of
('session', 'local', ''). In the first two cases, only
sessionStorage resp. localStorage is counted. If it's an empty
string (the default) then both are counted. Only storage keys
which match the pattern used by kvvfs are counted. The returned
value is the "length" value of every matching key and value,
noting that JavaScript stores each character in 2 bytes.
Note that the returned size is not authoritative from the
perspective of how much data can fit into localStorage and
sessionStorage, as the precise algorithms for determining
those limits are unspecified and may include per-entry
overhead invisible to clients.
*/
capi.sqlite3_web_kvvfs_size = function(which=''){
let sz = 0;
const kvinfo = __kvvfsInfo(which);
kvinfo.stores.forEach((s)=>{
let i;
for(i = 0; i < s.length; ++i){
const k = s.key(i);
if(k.startsWith(kvinfo.prefix)){
sz += k.length;
sz += s.getItem(k).length;
}
}
});
return sz * 2 /* because JS uses UC16 encoding */;
};
/**
Given an `sqlite3*`, returns a truthy value (see below) if that
db handle uses the "kvvfs" VFS, else returns false. If pDb is
NULL then this function returns true if the default VFS is
"kvvfs". Results are undefined if pDb is truthy but refers to
an invalid pointer.
The truthy value it returns is a pointer to the kvvfs
`sqlite3_vfs` object.
*/
capi.sqlite3_web_db_is_kvvfs = function(pDb){
const pK = capi.sqlite3_vfs_find("kvvfs");
if(!pK) return false;
else if(!pDb){
return capi.sqlite3_vfs_find(0) && pK;
}
const scope = capi.wasm.scopedAllocPush();
try{
const ppVfs = capi.wasm.scopedAllocPtr();
return (
(0===capi.sqlite3_file_control(
pDb, "main", capi.SQLITE_FCNTL_VFS_POINTER, ppVfs
)) && (capi.wasm.getPtrValue(ppVfs) === pK)
) ? pK : false;
}finally{
capi.wasm.scopedAllocPop(scope);
}
};
}/* main-window-only bits */
/* The remainder of the API will be set up in later steps. */
const sqlite3 = {
WasmAllocError: WasmAllocError,
capi,
config
};
sqlite3ApiBootstrap.initializers.forEach((f)=>f(sqlite3));
delete sqlite3ApiBootstrap.initializers;
sqlite3ApiBootstrap.sqlite3 = sqlite3;
return sqlite3;
}/*sqlite3ApiBootstrap()*/;
/**
self.sqlite3ApiBootstrap.initializers is an internal detail used by
the various pieces of the sqlite3 API's amalgamation process. It
must not be modified by client code except when plugging such code
into the amalgamation process.
Each component of the amalgamation is expected to append a function
to this array. When sqlite3ApiBootstrap() is called for the first
time, each such function will be called (in their appended order)
and passed the sqlite3 namespace object, into which they can install
their features (noting that most will also require that certain
features alread have been installed). At the end of that process,
this array is deleted.
*/
self.sqlite3ApiBootstrap.initializers = [];
/**
Client code may assign sqlite3ApiBootstrap.defaultConfig an
object-type value before calling sqlite3ApiBootstrap() (without
arguments) in order to tell that call to use this object as its
default config value. The intention of this is to provide
downstream clients with a reasonably flexible approach for plugging in
an environment-suitable configuration without having to define a new
global-scope symbol.
*/
self.sqlite3ApiBootstrap.defaultConfig = Object.create(null);
/**
Placeholder: gets installed by the first call to
self.sqlite3ApiBootstrap(). However, it is recommended that the
caller of sqlite3ApiBootstrap() capture its return value and delete
self.sqlite3ApiBootstrap after calling it. It returns the same
value which will be stored here.
*/
self.sqlite3ApiBootstrap.sqlite3 = undefined;