1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-12-24 14:17:58 +03:00

Merge trunk into the sahpool-digest branch.

FossilOrigin-Name: fc1eeb7d1f2880907b0fe71a8c572dd7cd74a5d65ec0177332976ad2f8c2b216
This commit is contained in:
stephan
2025-02-26 03:32:52 +00:00
151 changed files with 5772 additions and 3530 deletions

View File

@@ -8,16 +8,16 @@
point the sqlite3 JS API bits will get set up.
*/
Module.runSQLite3PostLoadInit = function(EmscriptenModule/*the Emscripten-style module object*/){
/** ^^^ As don't use Module.postRun, as that runs a different time
/** ^^^ Don't use Module.postRun, as that runs a different time
depending on whether this file is built with emcc 3.1.x or
4.0.x. This function name is intentionally obnoxiously verbose to
ensure that we don't collide with current and future Emscripten
symbol names. */
'use strict';
//console.warn("This is the start of the Module.postRun handler.");
//console.warn("This is the start of Module.runSQLite3PostLoadInit()");
/* This function will contain at least the following:
- post-js-header.js (this file)
- post-js-header.js => this file
- sqlite3-api-prologue.js => Bootstrapping bits to attach the rest to
- common/whwasmutil.js => Replacements for much of Emscripten's glue
- jaccwabyt/jaccwabyt.js => Jaccwabyt (C/JS struct binding)
@@ -26,8 +26,8 @@ Module.runSQLite3PostLoadInit = function(EmscriptenModule/*the Emscripten-style
- sqlite3-api-worker1.js => Worker-based API
- sqlite3-vfs-helper.c-pp.js => Utilities for VFS impls
- sqlite3-vtab-helper.c-pp.js => Utilities for virtual table impls
- sqlite3-vfs-opfs.c-pp.js => OPFS VFS
- sqlite3-vfs-opfs.c-pp.js => OPFS VFS
- sqlite3-vfs-opfs-sahpool.c-pp.js => OPFS SAHPool VFS
- sqlite3-api-cleanup.js => final API cleanup
- post-js-footer.js => closes this postRun() function
- post-js-footer.js => closes this function
*/

View File

@@ -229,14 +229,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
'*'
]],
/**
2025-02-03: We do not have a way to automatically clean up
destructors which are automatically converted from JS functions
via the final argument to sqlite3_set_auxdata(). Because of
that, it is strongly recommended that clients use
wasm.installFunction() to create such callbacks, then pass that
pointer to sqlite3_set_auxdata(). Relying on automated
conversions here will lead to leaks of JS/WASM proxy functions
because sqlite3_set_auxdata() is frequently called in UDFs.
We do not have a way to automatically clean up destructors
which are automatically converted from JS functions via the
final argument to sqlite3_set_auxdata(). Because of that,
automatic function conversion is not supported for this
function. Clients should use wasm.installFunction() to create
such callbacks, then pass that pointer to
sqlite3_set_auxdata(). Relying on automated conversions here
would lead to leaks of JS/WASM proxy functions because
sqlite3_set_auxdata() is frequently called in UDFs.
The sqlite3.oo1.DB class's onclose handlers can be used for this
purpose. For example:
@@ -252,14 +253,24 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Then pass pAuxDtor as the final argument to appropriate
sqlite3_set_auxdata() calls.
Note that versions prior to 3.49.0 ostensibly had automatic
function conversion here but a typo prevented it from
working. Rather than fix it, it was removed because testing the
fix brought the huge potential for memory leaks to the
forefront.
*/
["sqlite3_set_auxdata", undefined, [
"sqlite3_context*", "int", "*",
new wasm.xWrap.FuncPtrAdapter({
name: 'xDestroyAuxData',
signature: 'v(p)',
contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */]
})
true
? "*"
: new wasm.xWrap.FuncPtrAdapter({
/* If we can find a way to automate their cleanup, JS functions can
be auto-converted with this. */
name: 'xDestroyAuxData',
signature: 'v(p)',
contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */]
})
]],
["sqlite3_shutdown", undefined],
["sqlite3_sourceid", "string"],

View File

@@ -12,12 +12,12 @@
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()-style handler. The
sqlite3 JS API has no hard requirements on Emscripten and does not
expose any Emscripten APIs to clients. It is structured such that
its build can be tweaked to include it in arbitrary WASM
environments which can supply the necessary underlying features
(e.g. a POSIX file I/O layer).
whole file into a single callback which can be run after Emscripten
loads the corresponding WASM module. The sqlite3 JS API has no hard
requirements on Emscripten and does not expose any Emscripten APIs
to clients. It is structured such that its build can be tweaked to
include it in arbitrary WASM environments which can supply the
necessary underlying features (e.g. a POSIX file I/O layer).
Main project home page: https://sqlite.org
@@ -1453,7 +1453,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
creates (or overwrites) the given file using those APIs. This is
primarily intended for use in Emscripten-based builds where the POSIX
APIs are transparently proxied by an in-memory virtual filesystem.
It may behave diffrently in other environments.
It may behave differently in other environments.
The first argument must be either a JS string or WASM C-string
holding the filename. Note that this routine does _not_ create

View File

@@ -279,11 +279,11 @@
The arguments are in the same form accepted by oo1.DB.exec(), with
the exceptions noted below.
If the `countChanges` arguments property (added in version 3.43) is
truthy then the `result` property contained by the returned object
will have a `changeCount` property which holds the number of changes
made by the provided SQL. Because the SQL may contain an arbitrary
number of statements, the `changeCount` is calculated by calling
If `args.countChanges` (added in version 3.43) is truthy then the
`result` property contained by the returned object will have a
`changeCount` property which holds the number of changes made by the
provided SQL. Because the SQL may contain an arbitrary number of
statements, the `changeCount` is calculated by calling
`sqlite3_total_changes()` before and after the SQL is evaluated. If
the value of `countChanges` is 64 then the `changeCount` property
will be returned as a 64-bit integer in the form of a BigInt (noting
@@ -292,6 +292,15 @@
calling `sqlite3_total_changes64()` before and after the SQL is
evaluated.
If the `args.lastInsertRowId` (added in version 3.50.0) is truthy
then the `result` property contained by the returned object will
have a `lastInsertRowId` will hold a BigInt-type value corresponding
to the result of sqlite3_last_insert_rowid(). This value is only
fetched once, after the SQL is run, regardless of how many
statements the SQL contains. This API has no idea whether the SQL
contains any INSERTs, so it is up to the client to apply/rely on
this property only when it makes sense to do so.
A function-type args.callback property cannot cross
the window/Worker boundary, so is not useful here. If
args.callback is a string then it is assumed to be a
@@ -542,6 +551,12 @@ sqlite3.initWorker1API = function(){
if(undefined !== changeCount){
rc.changeCount = db.changes(true,64===rc.countChanges) - changeCount;
}
const lastInsertRowId = !!rc.lastInsertRowId
? sqlite3.capi.sqlite3_last_insert_rowid(db)
: undefined;
if( undefined!==lastInsertRowId ){
rc.lastInsertRowId = lastInsertRowId;
}
if(rc.callback instanceof Function){
rc.callback = theCallback;
/* Post a sentinel message to tell the client that the end

View File

@@ -544,22 +544,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
currently-opened client-specified filenames. */
getFileNames(){
const rc = [];
const iter = this.#mapFilenameToSAH.keys();
for(const n of iter) rc.push(n);
for(const n of this.#mapFilenameToSAH.keys()) rc.push(n);
return rc;
}
// #createFileObject(sah,clientName,opaqueName){
// const f = Object.assign(Object.create(null),{
// clientName, opaqueName
// });
// this.#mapSAHToMeta.set(sah, f);
// return f;
// }
// #unmapFileObject(sah){
// this.#mapSAHToMeta.delete(sah);
// }
/**
Adds n files to the pool's capacity. This change is
persistent across settings. Returns a Promise which resolves
@@ -600,8 +588,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
/**
Releases all currently-opened SAHs. The only legal
operation after this is acquireAccessHandles().
Releases all currently-opened SAHs. The only legal operation
after this is acquireAccessHandles() or (if this is called from
pauseVfs()) either of isPaused() or unpauseVfs().
*/
releaseAccessHandles(){
for(const ah of this.#mapSAHToName.keys()) ah.close();
@@ -611,17 +600,21 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
/**
Opens all files under this.vfsDir/this.#dhOpaque and acquires
a SAH for each. returns a Promise which resolves to no value
but completes once all SAHs are acquired. If acquiring an SAH
throws, SAHPool.$error will contain the corresponding
exception.
Opens all files under this.vfsDir/this.#dhOpaque and acquires a
SAH for each. Returns a Promise which resolves to no value but
completes once all SAHs are acquired. If acquiring an SAH
throws, this.$error will contain the corresponding Error
object.
If it throws, it releases any SAHs which it may have
acquired before the exception was thrown, leaving the VFS in a
well-defined but unusable state.
If clearFiles is true, the client-stored state of each file is
cleared when its handle is acquired, including its name, flags,
and any data stored after the metadata block.
*/
async acquireAccessHandles(clearFiles){
async acquireAccessHandles(clearFiles=false){
const files = [];
for await (const [name,h] of this.#dhOpaque){
if('file'===h.kind){
@@ -890,12 +883,18 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Removes this object's sqlite3_vfs registration and shuts down
this object, releasing all handles, mappings, and whatnot,
including deleting its data directory. There is currently no
way to "revive" the object and reaquire its resources.
way to "revive" the object and reaquire its
resources. Similarly, there is no recovery strategy if removal
of any given SAH fails, so such errors are ignored by this
function.
This function is intended primarily for testing.
Resolves to true if it did its job, false if the
VFS has already been shut down.
@see pauseVfs()
@see unpauseVfs()
*/
async removeVfs(){
if(!this.#cVfs.pointer || !this.#dhOpaque) return false;
@@ -911,13 +910,77 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
);
this.#dhVfsRoot = this.#dhVfsParent = undefined;
}catch(e){
sqlite3.config.error(this.vfsName,"removeVfs() failed:",e);
sqlite3.config.error(this.vfsName,"removeVfs() failed with no recovery strategy:",e);
/*otherwise ignored - there is no recovery strategy*/
}
return true;
}
/**
"Pauses" this VFS by unregistering it from SQLite and
relinquishing all open SAHs, leaving the associated files
intact. If this object is already paused, this is a
no-op. Returns this object.
This function throws if SQLite has any opened file handles
hosted by this VFS, as the alternative would be to invoke
Undefined Behavior by closing file handles out from under the
library. Similarly, automatically closing any database handles
opened by this VFS would invoke Undefined Behavior in
downstream code which is holding those pointers.
If this function throws due to open file handles then it has
no side effects. If the OPFS API throws while closing handles
then the VFS is left in an undefined state.
@see isPaused()
@see unpauseVfs()
*/
pauseVfs(){
if(this.#mapS3FileToOFile_.size>0){
sqlite3.SQLite3Error.toss(
capi.SQLITE_MISUSE, "Cannot pause VFS",
this.vfsName,"because it has opened files."
);
}
if(this.#mapSAHToName.size>0){
capi.sqlite3_vfs_unregister(this.vfsName);
this.releaseAccessHandles();
}
return this;
}
/**
Returns true if this pool is currently paused else false.
@see pauseVfs()
@see unpauseVfs()
*/
isPaused(){
return 0===this.#mapSAHToName.size;
}
/**
"Unpauses" this VFS, reacquiring all SAH's and (if successful)
re-registering it with SQLite. This is a no-op if the VFS is
not currently paused.
The returned Promise resolves to this object. See
acquireAccessHandles() for how it behaves if it throws due to
SAH acquisition failure.
@see isPaused()
@see pauseVfs()
*/
async unpauseVfs(){
if(0===this.#mapSAHToName.size){
return this.acquireAccessHandles(false).
then(()=>capi.sqlite3_vfs_register(this.#cVfs, 0),this);
}
return this;
}
//! Documented elsewhere in this file.
exportFile(name){
const sah = this.#mapFilenameToSAH.get(name) || toss("File not found:",name);
@@ -1042,6 +1105,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
async removeVfs(){ return this.#p.removeVfs() }
pauseVfs(){ this.#p.pauseVfs(); return this; }
async unpauseVfs(){ return this.#p.unpauseVfs().then(()=>this); }
isPaused(){ return this.#p.isPaused() }
}/* class OpfsSAHPoolUtil */;
/**
@@ -1275,6 +1342,41 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Clears all client-defined state of all SAHs and makes all of them
available for re-use by the pool. Results are undefined if any such
handles are currently in use, e.g. by an sqlite3 db.
APIs specific to the "pause" capability (added in version 3.49):
Summary: "pausing" the VFS disassociates it from SQLite and
relinquishes its SAHs so that they may be opened by another
instance of this VFS (running in a separate tab/page or Worker).
"Unpausing" it takes back control, if able.
- pauseVfs()
"Pauses" this VFS by unregistering it from SQLite and
relinquishing all open SAHs, leaving the associated files intact.
This enables pages/tabs to coordinate semi-concurrent usage of
this VFS. If this object is already paused, this is a
no-op. Returns this object. Throws if SQLite has any opened file
handles hosted by this VFS. If this function throws due to open
file handles then it has no side effects. If the OPFS API throws
while closing handles then the VFS is left in an undefined state.
- isPaused()
Returns true if this VFS is paused, else false.
- [async] unpauseVfs()
Restores the VFS to an active state after having called
pauseVfs() on it. This is a no-op if the VFS is not paused. The
returned Promise resolves to this object on success. A rejected
Promise means there was a problem reacquiring the SAH handles
(possibly because they're in use by another instance or have
since been removed). Generically speaking, there is no recovery
strategy for that type of error, but if the problem is simply
that the OPFS files are locked, then a later attempt to unpause
it, made after the concurrent instance releases the SAHs, may
recover from the situation.
*/
sqlite3.installOpfsSAHPoolVfs = async function(options=Object.create(null)){
options = Object.assign(Object.create(null), optionDefaults, (options||{}));

View File

@@ -459,7 +459,7 @@ const installOpfsVfs = function callee(options){
Runs the given operation (by name) in the async worker
counterpart, waits for its response, and returns the result
which the async worker writes to SAB[state.opIds.rc]. The
2nd and subsequent arguments must be the aruguments for the
2nd and subsequent arguments must be the arguments for the
async op.
*/
const opRun = (op,...args)=>{

View File

@@ -980,7 +980,7 @@ const char * sqlite3__wasm_enum_json(void){
#undef _DefGroup
/*
** Emit an array of "StructBinder" struct descripions, which look
** Emit an array of "StructBinder" struct descriptions, which look
** like:
**
** {

View File

@@ -335,8 +335,8 @@ sqlite3Worker1Promiser.v2 = function(config){
/**
When built as a module, we export sqlite3Worker1Promiser.v2()
instead of sqlite3Worker1Promise() because (A) its interface is more
conventional for ESM usage and (B) the ESM option export option for
this API did not exist until v2 was created, so there's no backwards
conventional for ESM usage and (B) the ESM export option for this
API did not exist until v2 was created, so there's no backwards
incompatibility.
*/
export default sqlite3Worker1Promiser.v2;