1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Experimentally add sqlite3.oo1.DB/Stmt.wrapHandle(), which allow DB/Stmt instances to wrap a (sqlite3*)/(sqlite3_stmt*) optionally with or without taking ownership of it. The intent is to enable mix-and-match use of the C API, the oo1 API, and any other hypothetical API which exposes those pointers. oo1.Stmt.parameterCount is now a property access interceptor like Stmt.columnCount is, but that doesn't change how it's used.

FossilOrigin-Name: 1227543b87c3320d6b80e0f61b88ea53b68779966a0295c4d6a1db6369c48207
This commit is contained in:
stephan
2025-07-06 15:01:44 +00:00
parent 27408ab9c9
commit 4d453cc2ed
4 changed files with 346 additions and 84 deletions

View File

@ -37,6 +37,16 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
it.
*/
const __ptrMap = new WeakMap();
/**
A Set of oo1.DB objects which are proxies for (A) (sqlite3*) or
another oo1.DB object or (B) oo1.Stmt objects which are proxies
for (sqlite3_stmt*) pointers. Such objects do not own their
underlying handle and that handle must be guaranteed (by the
client) to outlive the proxy. These proxies are primarily
intended as a way to briefly wrap an (sqlite3[_stmt]*) object as
an oo1.DB/Stmt without taking over ownership.
*/
const __doesNotOwnHandle = new Set();
/**
Map of DB instances to objects, each object being a map of Stmt
wasm pointers to Stmt objects.
@ -234,73 +244,89 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
};
}
const opt = ctor.normalizeArgs(...args);
let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags;
if(('string'!==typeof fn && 'number'!==typeof fn)
|| 'string'!==typeof flagsStr
|| (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){
sqlite3.config.error("Invalid DB ctor args",opt,arguments);
toss3("Invalid arguments for DB constructor.");
}
let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn;
const vfsCheck = ctor._name2vfs[fnJs];
if(vfsCheck){
vfsName = vfsCheck.vfs;
fn = fnJs = vfsCheck.filename(fnJs);
}
let pDb, oflags = 0;
if( flagsStr.indexOf('c')>=0 ){
oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
}
if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
oflags |= capi.SQLITE_OPEN_EXRESCODE;
const stack = wasm.pstack.pointer;
try {
const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0);
pDb = wasm.peekPtr(pPtr);
checkSqlite3Rc(pDb, rc);
capi.sqlite3_extended_result_codes(pDb, 1);
if(flagsStr.indexOf('t')>=0){
capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT,
__dbTraceToConsole, pDb);
//sqlite3.config.debug("DB ctor",opt);
let pDb;
if( (pDb = opt['sqlite3*']) ){
/* This property ^^^^^ is very specifically NOT DOCUMENTED and
NOT part of the public API. This is a back door for functions
like DB.wrapDbHandle(). */
//sqlite3.config.debug("creating proxy db from",opt);
if( !opt['sqlite3*:takeOwnership'] ){
/* This is object does not own its handle. */
__doesNotOwnHandle.add(this);
}
}catch( e ){
if( pDb ) capi.sqlite3_close_v2(pDb);
throw e;
}finally{
wasm.pstack.restore(stack);
this.filename = capi.sqlite3_db_filename(pDb,'main');
}else{
let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags;
if(('string'!==typeof fn && 'number'!==typeof fn)
|| 'string'!==typeof flagsStr
|| (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){
sqlite3.config.error("Invalid DB ctor args",opt,arguments);
toss3("Invalid arguments for DB constructor.");
}
let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn;
const vfsCheck = ctor._name2vfs[fnJs];
if(vfsCheck){
vfsName = vfsCheck.vfs;
fn = fnJs = vfsCheck.filename(fnJs);
}
let oflags = 0;
if( flagsStr.indexOf('c')>=0 ){
oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
}
if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
oflags |= capi.SQLITE_OPEN_EXRESCODE;
const stack = wasm.pstack.pointer;
try {
const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0);
pDb = wasm.peekPtr(pPtr);
checkSqlite3Rc(pDb, rc);
capi.sqlite3_extended_result_codes(pDb, 1);
if(flagsStr.indexOf('t')>=0){
capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT,
__dbTraceToConsole, pDb);
}
}catch( e ){
if( pDb ) capi.sqlite3_close_v2(pDb);
throw e;
}finally{
wasm.pstack.restore(stack);
}
this.filename = fnJs;
}
this.filename = fnJs;
__ptrMap.set(this, pDb);
__stmtMap.set(this, Object.create(null));
try{
if( !opt['sqlite3*'] ){
try{
//#if enable-see
dbCtorApplySEEKey(this,opt);
dbCtorApplySEEKey(this,opt);
//#endif
// Check for per-VFS post-open SQL/callback...
const pVfs = capi.sqlite3_js_db_vfs(pDb)
|| toss3("Internal error: cannot get VFS for new db handle.");
const postInitSql = __vfsPostOpenCallback[pVfs];
if(postInitSql){
/**
Reminder: if this db is encrypted and the client did _not_ pass
in the key, any init code will fail, causing the ctor to throw.
We don't actually know whether the db is encrypted, so we cannot
sensibly apply any heuristics which skip the init code only for
encrypted databases for which no key has yet been supplied.
*/
if(postInitSql instanceof Function){
postInitSql(this, sqlite3);
}else{
checkSqlite3Rc(
pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
);
// Check for per-VFS post-open SQL/callback...
const pVfs = capi.sqlite3_js_db_vfs(pDb)
|| toss3("Internal error: cannot get VFS for new db handle.");
const postInitSql = __vfsPostOpenCallback[pVfs];
if(postInitSql){
/**
Reminder: if this db is encrypted and the client did _not_ pass
in the key, any init code will fail, causing the ctor to throw.
We don't actually know whether the db is encrypted, so we cannot
sensibly apply any heuristics which skip the init code only for
encrypted databases for which no key has yet been supplied.
*/
if(postInitSql instanceof Function){
postInitSql(this, sqlite3);
}else{
checkSqlite3Rc(
pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
);
}
}
}catch(e){
this.close();
throw e;
}
}catch(e){
this.close();
throw e;
}
};
@ -486,26 +512,30 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- `db`: the DB object which created the statement.
- `columnCount`: the number of result columns in the query, or 0
for queries which cannot return results. This property is a proxy
for sqlite3_column_count() and its use in loops should be avoided
because of the call overhead associated with that. The
`columnCount` is not cached when the Stmt is created because a
schema change made via a separate db connection between this
statement's preparation and when it is stepped may invalidate it.
for queries which cannot return results. This property is a
read-only proxy for sqlite3_column_count() and its use in loops
should be avoided because of the call overhead associated with
that. The `columnCount` is not cached when the Stmt is created
because a schema change made between this statement's preparation
and when it is stepped may invalidate it.
- `parameterCount`: the number of bindable parameters in the query.
- `parameterCount`: the number of bindable parameters in the
query. Like `columnCount`, this property is ready-only and is a
proxy for a C API call.
As a general rule, most methods of this class will throw if
called on an instance which has been finalized. For brevity's
sake, the method docs do not all repeat this warning.
*/
const Stmt = function(){
const Stmt = function(/*oo1db, stmtPtr, BindTypes [,takeOwnership=true] */){
if(BindTypes!==arguments[2]){
toss3(capi.SQLITE_MISUSE, "Do not call the Stmt constructor directly. Use DB.prepare().");
}
this.db = arguments[0];
__ptrMap.set(this, arguments[1]);
this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer);
if( arguments.length>3 && false===arguments[3] ){
__doesNotOwnHandle.add(this);
}
};
/** Throws if the given DB has been closed, else it is returned. */
@ -723,12 +753,12 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
a db.
*/
close: function(){
if(this.pointer){
const pDb = this.pointer;
if(pDb){
if(this.onclose && (this.onclose.before instanceof Function)){
try{this.onclose.before(this)}
catch(e){/*ignore*/}
}
const pDb = this.pointer;
Object.keys(__stmtMap.get(this)).forEach((k,s)=>{
if(s && s.pointer){
try{s.finalize()}
@ -737,7 +767,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
});
__ptrMap.delete(this);
__stmtMap.delete(this);
capi.sqlite3_close_v2(pDb);
if( !__doesNotOwnHandle.delete(this) ){
capi.sqlite3_close_v2(pDb);
}
if(this.onclose && (this.onclose.after instanceof Function)){
try{this.onclose.after(this)}
catch(e){/*ignore*/}
@ -1450,9 +1482,87 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
*/
checkRc: function(resultCode){
return checkSqlite3Rc(this, resultCode);
}
},
}/*DB.prototype*/;
/**
Returns a new oo1.DB instance which wraps the given db.
The first argument must be either a non-NULL (sqlite3*) WASM
pointer or a non-close()d instance of oo1.DB.
The second argument only applies if the first argument is a
(sqlite3*). If it is, the returned object will pass that pointer
to sqlite3_close() when its close() method is called, otherwise
it will not.
If the first argument is a oo1.DB object, the second argument is
disregarded and the returned object will be created as a
sqlite3.oo1.DB object (as opposed to the concrete derived DB
subclass from the first argument), so will not include any
derived-type behaviors,
e.g. JsStorageDb.prototype.clearStorage().
Throws if db cannot be resolved to one of the legal options.
The caller MUST GUARANTEE that the passed-in handle will outlive
the returned object, i.e. that it will not be closed. If it is closed,
this object will hold a stale pointer and results are undefined.
Aside from its lifetime, the proxy is to be treated as any other
DB instance, including the requirement of calling close() on
it. close() will free up internal resources owned by the proxy,
and disassociate the proxy from that handle, but will not
actually close the proxied db handle.
The following quirks and requirements apply when proxying another
DB instance, as opposed to a (sqlite3*):
- DO NOT call close() on the being-proxied instance while a proxy
is active.
- ALWAYS eventually call close() on the returned object BEFORE
the being-proxied handle is closed.
- For historical reasons, the filename property of the returned
object is captured at the time of this call, as opposed to being
dynamically proxied. e.g., if the filename property of the
being-proxied object is changed, this object will not reflect
that change. There is no good reason to ever modify that
property, so this distinction is not truly significant but it's
noted here because it's a client-visible discrepancy between the
proxy and its partner. (Sidebar: the filename property _should_
be a property access interceptor for sqlite3_db_filename(),
but making it so now may break existing code.)
*/
DB.wrapHandle = function(db, takeOwnership=false){
let ptr, ctor = DB;
const oo1db = (db instanceof DB) ? db : undefined;
if( wasm.isPtr(db) ){
ptr = db;
}else if( oo1db ){
takeOwnership = false;
ptr = db.pointer;
//ctor = db.constructor;
// ^^^ that doesn't work, resulting in an Object-type value
}
//sqlite3.config.debug("wrapHandle()",'db',db,'ctor',ctor,
//'arguments',arguments,'db.constructor',db.constructor);
if( !ptr ){
throw new sqlite3.SQLite3Error(sqlite3.SQLITE_MISUSE,
"Argument must be a WASM sqlite3 "+
"pointer or an sqlite3.oo1.DB instance");
}
const dc = new ctor({
"sqlite3*": ptr,
"sqlite3*:takeOwnership": !!takeOwnership
});
if( oo1db ){
dc.filename = oo1db.filename;
}//else dc.filename was captured by the ctor for legacy consistency
//sqlite3.config.debug("wrapHandle() dc",dc);
return dc;
}/*DB.wrapHandle()*/;
/** Throws if the given Stmt has been finalized, else stmt is
returned. */
@ -1641,12 +1751,19 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
This method always throws if called when it is illegal to do
so. Namely, when triggered via a per-row callback handler of a
DB.exec() call.
If Stmt does not own its underlying (sqlite3_stmt*) (see
Stmt.wrapHandle()) then this function will not pass it to
sqlite3_finalize().
*/
finalize: function(){
if(this.pointer){
const ptr = this.pointer;
if(ptr){
affirmNotLockedByExec(this,'finalize()');
const rc = capi.sqlite3_finalize(this.pointer);
delete __stmtMap.get(this.db)[this.pointer];
const rc = (__doesNotOwnHandle.delete(this)
? 0
: capi.sqlite3_finalize(ptr));
delete __stmtMap.get(this.db)[ptr];
__ptrMap.delete(this);
__execLock.delete(this);
__stmtMayGet.delete(this);
@ -2134,6 +2251,60 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
set: ()=>toss3("The columnCount property is read-only.")
});
Object.defineProperty(Stmt.prototype, 'parameterCount', {
enumerable: false,
get: function(){return capi.sqlite3_bind_parameter_count(this.pointer)},
set: ()=>toss3("The parameterCount property is read-only.")
});
/**
The Stmt counterpart of oo1.DB.wrapHandle(), this creates a Stmt
instance which wraps a WASM (sqlite3_stmt*) in the oo1 API with
or without taking over ownership of that pointer.
The first argument must be an oo1.DB instance[^1].
The second argument must be a valid WASM (sqlite3_stmt*), as
produced by sqlite3_prepare_v2() and sqlite3_prepare_v3().
The third argument specifies whether the returned Stmt object
takes over ownership of the underlying (sqlite3_stmt*). If true,
the returned object's finalize() method will finalize that
handle, else it will not. If it is false, ownership of stmtPtr is
unchanged and stmtPtr MUST outlive the returned object or results
are undefined.
This function throws if the arguments are invalid. On success it
returns a new Stmt object which wraps the given statement
pointer.
Like all Stmt objects, the finalize() method must eventually be
called on the returned object to free up internal resources,
regardless of whether this function's third argument is true or
not.
[^1]: The first argument cannot be a (sqlite3*) because the
resulting Stmt object requires a parent DB object. It is not yet
determined whether it would be of general benefit to refactor the
DB/Stmt pair internals to communicate in terms of the underlying
(sqlite3*) rather than a DB object. If so, we could laxen the
first argument's requirement and allow an (sqlite3*).
*/
Stmt.wrapHandle = function(oo1db, stmtPtr, takeOwnership=false){
let ctor = Stmt;
if( !(oo1db instanceof DB) || !oo1db.pointer ){
throw new sqlite3.SQLite3Error(sqlite3.SQLITE_MISUSE,
"First argument must be an opened "+
"sqlite3.oo1.DB instance");
}
if( !stmtPtr || !wasm.isPtr(stmtPtr) ){
throw new sqlite3.SQLite3Error(sqlite3.SQLITE_MISUSE,
"Second argument must be a WASM "+
"sqlite3_stmt pointer");
}
return new Stmt(oo1db, stmtPtr, BindTypes, !!takeOwnership);
}
/** The OO API's public namespace. */
sqlite3.oo1 = {
DB,

View File

@ -41,7 +41,7 @@
ES6 worker module build:
./c-pp -f tester1.c-pp.js -o tester1-esm.js -Dtarget=es6-module
./c-pp -f tester1.c-pp.js -o tester1-esm.mjs -Dtarget=es6-module
*/
//#if target=es6-module
import {default as sqlite3InitModule} from './jswasm/sqlite3.mjs';
@ -1209,6 +1209,94 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
}
}
})
////////////////////////////////////////////////////////////////////
.t({
name: "oo1.DB/Stmt.wrapDbHandle()",
test: function(sqlite3){
/* Maintenance reminder: this function is early in the list to
demonstrate that the wrappers for this.db created by this
function do not interfere with downstream tests, e.g. by
closing this.db.pointer. */
sqlite3.config.debug("Proxying",this.db);
let dw = sqlite3.oo1.DB.wrapHandle(this.db);
sqlite3.config.debug('dw',dw);
T.assert( dw, '!!dw' )
.assert( dw instanceof sqlite3.oo1.DB, 'dw is-a oo1.DB' )
.assert( dw.pointer, 'dw.pointer' )
.assert( dw.pointer === this.db.pointer, 'dw.pointer===db.pointer' )
.assert( dw.filename === this.db.filename, 'dw.filename===db.filename' );
T.assert( dw === dw.exec("select 1") );
let q;
try {
q = dw.prepare("select 1");
T.assert( q.step() )
.assert( !q.step() );
}finally{
if( q ) q.finalize();
}
dw.close();
T.assert( !dw.pointer )
.assert( this.db === this.db.exec("select 1") );
dw = undefined;
let pDb = 0, pStmt = 0;
const stack = wasm.pstack.pointer;
try {
const ppOut = wasm.pstack.allocPtr();
T.assert( 0===wasm.peekPtr(ppOut) );
let rc = capi.sqlite3_open_v2( ":memory:", ppOut,
capi.SQLITE_OPEN_CREATE
| capi.SQLITE_OPEN_READWRITE,
0);
T.assert( 0===rc, 'open_v2()' );
pDb = wasm.peekPtr(ppOut);
wasm.pokePtr(ppOut, 0);
T.assert( pDb>0, 'pDb>0' );
const pTmp = pDb;
dw = sqlite3.oo1.DB.wrapHandle(pDb, true);
pDb = 0;
//sqlite3.config.debug("dw",dw);
T.assert( pTmp===dw.pointer, 'pDb===dw.pointer' );
T.assert( dw.filename === "", "dw.filename == "+dw.filename );
let q = dw.prepare("select 1");
try {
T.assert( q.step(), "step()" );
T.assert( !q.step(), "!step()" );
}finally{
q.finalize();
q = undefined;
}
T.assert( dw===dw.exec("select 1") );
dw.affirmOpen();
rc = capi.sqlite3_prepare_v2( dw, "select 1", -1, ppOut, 0 );
T.assert( 0===rc, 'prepare_v2() rc='+rc );
pStmt = wasm.peekPtr(ppOut);
try {
q = sqlite3.oo1.Stmt.wrapHandle(dw, pStmt, false);
T.assert( q.step(), "step()" )
.assert( !q.step(), "!step()" );
q.finalize();
q = undefined;
q = sqlite3.oo1.Stmt.wrapHandle(dw, pStmt, true);
pStmt = 0;
q.reset();
T.assert( q.step(), "step()" )
.assert( !q.step(), "!step()" );
}finally{
if( pStmt ) capi.sqlite3_finalize(pStmt)
if( q ) q.finalize();
}
}finally{
wasm.pstack.restore(stack);
if( pDb ){ capi.sqlite3_close_v2(pDb); }
else if( dw ){ dw.close(); }
}
}
})/*oo1.DB/Stmt.wrapHandle()*/
////////////////////////////////////////////////////////////////////
.t('sqlite3_db_config() and sqlite3_db_status()', function(sqlite3){
let rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, 0, 0);

View File

@ -1,5 +1,5 @@
C Add\s'reconfigure'\starget\sto\sMakefile.in\sto\sre-run\sthe\sconfigure\sscript\swith\sthe\ssame\sflags\sit\swas\sgenerated\swith.
D 2025-07-04T18:32:18.912
C Experimentally\sadd\ssqlite3.oo1.DB/Stmt.wrapHandle(),\swhich\sallow\sDB/Stmt\sinstances\sto\swrap\sa\s(sqlite3*)/(sqlite3_stmt*)\soptionally\swith\sor\swithout\staking\sownership\sof\sit.\sThe\sintent\sis\sto\senable\smix-and-match\suse\sof\sthe\sC\sAPI,\sthe\soo1\sAPI,\sand\sany\sother\shypothetical\sAPI\swhich\sexposes\sthose\spointers.\soo1.Stmt.parameterCount\sis\snow\sa\sproperty\saccess\sinterceptor\slike\sStmt.columnCount\sis,\sbut\sthat\sdoesn't\schange\show\sit's\sused.
D 2025-07-06T15:01:44.333
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@ -641,7 +641,7 @@ F ext/wasm/api/post-js-header.js 53740d824e5d9027eb1e6fd59e216abbd2136740ce260ea
F ext/wasm/api/pre-js.c-pp.js a614a2c82b12c4d96d8e3ba77330329efc53c4d56a8a7e60ade900f341866cfb
F ext/wasm/api/sqlite3-api-cleanup.js 3ac1786e461ada63033143be8c3b00b26b939540661f3e839515bb92f2e35359
F ext/wasm/api/sqlite3-api-glue.c-pp.js 0b76510f3650053bac67ca8947cb6ab9d050ad2218118a2e7796dd37be832ffa
F ext/wasm/api/sqlite3-api-oo1.c-pp.js c68d6da0088c2527156fca9163a721abe08e7bd077b15404fd8d292f4612adc1
F ext/wasm/api/sqlite3-api-oo1.c-pp.js 44d122b6d22ba9caa644193357a03bf5d2678a7815f1e2bbbdb086b14db11b7e
F ext/wasm/api/sqlite3-api-prologue.js 8708570165f5b4bce9a78ccd91bc9ddf8735970ac1c4d659e36c9a7d9a644bb4
F ext/wasm/api/sqlite3-api-worker1.c-pp.js f646a65257973b8c4481f8a6a216370b85644f23e64b126e7ae113570587c0ab
F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89
@ -698,7 +698,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c
F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c
F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2
F ext/wasm/tester1.c-pp.js 766a2ba51a2619d41a49be7c6a1ad014c1d23fc97b67496e4f103038203eb17d
F ext/wasm/tester1.c-pp.js e7176b2bc1228cf9a87833be7e53e20cb82dec2f104525f5cdd6cb1a53998ad6
F ext/wasm/tests/opfs/concurrency/index.html 657578a6e9ce1e9b8be951549ed93a6a471f4520a99e5b545928668f4285fb5e
F ext/wasm/tests/opfs/concurrency/test.js d08889a5bb6e61937d0b8cbb78c9efbefbf65ad09f510589c779b7cc6a803a88
F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@ -2208,8 +2208,11 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350
F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P 64f5f14322349b47451b8cac03bf8cd6f1ae45a8822e7f1ddee3d0b265047501
R 2e84b390408477c8e70efdd22e84c169
P c60907e77b32824aaaf024d299cdaf161b5f64fc927ffe5d5455eeb5754e6b01
R 23984087c5c98e4f1ed7d33ca211546f
T *branch * oo1-unowned-handles
T *sym-oo1-unowned-handles *
T -sym-trunk * Cancelled\sby\sbranch.
U stephan
Z a6be9728f4a0aec0de70108286e76b7e
Z 3bad3f183e619bbd50d1bebd9118b331
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
c60907e77b32824aaaf024d299cdaf161b5f64fc927ffe5d5455eeb5754e6b01
1227543b87c3320d6b80e0f61b88ea53b68779966a0295c4d6a1db6369c48207