1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

wasm: introduce the sqlite3.oo1.DB.wrapHandle() and Stmt.wrapHandle() APIs, which enable clients to wrap (sqlite3*) resp. (sqlite3_stmt*) pointers in their oo1 API counterparts, optionally with or without taking over ownership of the pointer.

FossilOrigin-Name: e5d079549594ca44852773b8919894866394e47ad725dadc7f65242413a219d3
This commit is contained in:
stephan
2025-07-11 19:52:36 +00:00
5 changed files with 359 additions and 99 deletions

View File

@ -429,7 +429,7 @@ define SQLITE.CALL.C-PP.FILTER
$(2): $(1) $$(MAKEFILE_LIST) $$(bin.c-pp) $(2): $(1) $$(MAKEFILE_LIST) $$(bin.c-pp)
@mkdir -p $$(dir $$@) @mkdir -p $$(dir $$@)
$$(bin.c-pp) -f $(1) -o $$@ $(3) $(SQLITE.CALL.C-PP.FILTER.global) $$(bin.c-pp) -f $(1) -o $$@ $(3) $(SQLITE.CALL.C-PP.FILTER.global)
#CLEAN_FILES += $(2) CLEAN_FILES += $(2)
endef endef
# /end SQLITE.CALL.C-PP.FILTER # /end SQLITE.CALL.C-PP.FILTER
######################################################################## ########################################################################

View File

@ -37,6 +37,21 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
it. it.
*/ */
const __ptrMap = new WeakMap(); const __ptrMap = new WeakMap();
/**
A Set of oo1.DB or oo1.Stmt objects which are proxies for
(sqlite3*) resp. (sqlite3_stmt*) pointers which themselves are
owned elsewhere. Objects in this Set do not own their underlying
handle and that handle must be guaranteed (by the client) to
outlive the proxy. DB.close()/Stmt.finalize() methods will remove
the object from this Set _instead_ of closing/finalizing the
pointer. These proxies are primarily intended as a way to briefly
wrap an (sqlite3[_stmt]*) object as an oo1.DB/Stmt without taking
over ownership, to take advantage of simplifies usage compared to
the C API while not imposing any change of ownership.
See DB.wrapHandle() and Stmt.wrapHandle().
*/
const __doesNotOwnHandle = new Set();
/** /**
Map of DB instances to objects, each object being a map of Stmt Map of DB instances to objects, each object being a map of Stmt
wasm pointers to Stmt objects. wasm pointers to Stmt objects.
@ -234,73 +249,89 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}; };
} }
const opt = ctor.normalizeArgs(...args); const opt = ctor.normalizeArgs(...args);
let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags; //sqlite3.config.debug("DB ctor",opt);
if(('string'!==typeof fn && 'number'!==typeof fn) let pDb;
|| 'string'!==typeof flagsStr if( (pDb = opt['sqlite3*']) ){
|| (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){ /* This property ^^^^^ is very specifically NOT DOCUMENTED and
sqlite3.config.error("Invalid DB ctor args",opt,arguments); NOT part of the public API. This is a back door for functions
toss3("Invalid arguments for DB constructor."); like DB.wrapDbHandle(). */
} //sqlite3.config.debug("creating proxy db from",opt);
let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn; if( !opt['sqlite3*:takeOwnership'] ){
const vfsCheck = ctor._name2vfs[fnJs]; /* This is object does not own its handle. */
if(vfsCheck){ __doesNotOwnHandle.add(this);
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);
} }
}catch( e ){ this.filename = capi.sqlite3_db_filename(pDb,'main');
if( pDb ) capi.sqlite3_close_v2(pDb); }else{
throw e; let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags;
}finally{ if(('string'!==typeof fn && 'number'!==typeof fn)
wasm.pstack.restore(stack); || '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); __ptrMap.set(this, pDb);
__stmtMap.set(this, Object.create(null)); __stmtMap.set(this, Object.create(null));
try{ if( !opt['sqlite3*'] ){
try{
//#if enable-see //#if enable-see
dbCtorApplySEEKey(this,opt); dbCtorApplySEEKey(this,opt);
//#endif //#endif
// Check for per-VFS post-open SQL/callback... // Check for per-VFS post-open SQL/callback...
const pVfs = capi.sqlite3_js_db_vfs(pDb) const pVfs = capi.sqlite3_js_db_vfs(pDb)
|| toss3("Internal error: cannot get VFS for new db handle."); || toss3("Internal error: cannot get VFS for new db handle.");
const postInitSql = __vfsPostOpenCallback[pVfs]; const postInitSql = __vfsPostOpenCallback[pVfs];
if(postInitSql){ if(postInitSql){
/** /**
Reminder: if this db is encrypted and the client did _not_ pass 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. 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 We don't actually know whether the db is encrypted, so we cannot
sensibly apply any heuristics which skip the init code only for sensibly apply any heuristics which skip the init code only for
encrypted databases for which no key has yet been supplied. encrypted databases for which no key has yet been supplied.
*/ */
if(postInitSql instanceof Function){ if(postInitSql instanceof Function){
postInitSql(this, sqlite3); postInitSql(this, sqlite3);
}else{ }else{
checkSqlite3Rc( checkSqlite3Rc(
pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0) pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
); );
}
} }
}catch(e){
this.close();
throw e;
} }
}catch(e){
this.close();
throw e;
} }
}; };
@ -403,7 +434,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- `vfs`: the VFS fname - `vfs`: the VFS fname
//#if enable-see //#if enable-see
SEE-capable builds optionally support ONE of the following SEE-capable builds optionally support ONE of the following
additional options: additional options:
@ -429,7 +459,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
is supplied and the database is encrypted, execution of the is supplied and the database is encrypted, execution of the
post-initialization SQL will fail, causing the constructor to post-initialization SQL will fail, causing the constructor to
throw. throw.
//#endif enable-see //#endif enable-see
The `filename` and `vfs` arguments may be either JS strings or The `filename` and `vfs` arguments may be either JS strings or
@ -457,8 +486,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/** /**
Internal-use enum for mapping JS types to DB-bindable types. Internal-use enum for mapping JS types to DB-bindable types.
These do not (and need not) line up with the SQLITE_type These do not (and need not) line up with the SQLITE_type
values. All values in this enum must be truthy and distinct values. All values in this enum must be truthy and (mostly)
but they need not be numbers. distinct but they need not be numbers.
*/ */
const BindTypes = { const BindTypes = {
null: 1, null: 1,
@ -467,7 +496,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
boolean: 4, boolean: 4,
blob: 5 blob: 5
}; };
BindTypes['undefined'] == BindTypes.null;
if(wasm.bigIntEnabled){ if(wasm.bigIntEnabled){
BindTypes.bigint = BindTypes.number; BindTypes.bigint = BindTypes.number;
} }
@ -486,26 +514,30 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- `db`: the DB object which created the statement. - `db`: the DB object which created the statement.
- `columnCount`: the number of result columns in the query, or 0 - `columnCount`: the number of result columns in the query, or 0
for queries which cannot return results. This property is a proxy for queries which cannot return results. This property is a
for sqlite3_column_count() and its use in loops should be avoided read-only proxy for sqlite3_column_count() and its use in loops
because of the call overhead associated with that. The should be avoided because of the call overhead associated with
`columnCount` is not cached when the Stmt is created because a that. The `columnCount` is not cached when the Stmt is created
schema change made via a separate db connection between this because a schema change made between this statement's preparation
statement's preparation and when it is stepped may invalidate it. 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 As a general rule, most methods of this class will throw if
called on an instance which has been finalized. For brevity's called on an instance which has been finalized. For brevity's
sake, the method docs do not all repeat this warning. 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]){ if(BindTypes!==arguments[2]){
toss3(capi.SQLITE_MISUSE, "Do not call the Stmt constructor directly. Use DB.prepare()."); toss3(capi.SQLITE_MISUSE, "Do not call the Stmt constructor directly. Use DB.prepare().");
} }
this.db = arguments[0]; this.db = arguments[0];
__ptrMap.set(this, arguments[1]); __ptrMap.set(this, arguments[1]);
this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer); if( arguments.length>3 && !arguments[3] ){
__doesNotOwnHandle.add(this);
}
}; };
/** Throws if the given DB has been closed, else it is returned. */ /** Throws if the given DB has been closed, else it is returned. */
@ -698,10 +730,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}, },
/** /**
Finalizes all open statements and closes this database Finalizes all open statements and closes this database
connection. This is a no-op if the db has already been connection (with one exception noted below). This is a no-op if
closed. After calling close(), `this.pointer` will resolve to the db has already been closed. After calling close(),
`undefined`, so that can be used to check whether the db `this.pointer` will resolve to `undefined`, and that can be
instance is still opened. used to check whether the db instance is still opened.
If this.onclose.before is a function then it is called before If this.onclose.before is a function then it is called before
any close-related cleanup. any close-related cleanup.
@ -721,14 +753,19 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
all, will never trigger close(), so onclose handlers are not a all, will never trigger close(), so onclose handlers are not a
reliable way to implement close-time cleanup or maintenance of reliable way to implement close-time cleanup or maintenance of
a db. a db.
If this instance was created using DB.wrapHandle() and does not
own this.pointer then it does not close the db handle but it
does perform all other work, such as calling onclose callbacks
and disassociating this object from this.pointer.
*/ */
close: function(){ close: function(){
if(this.pointer){ const pDb = this.pointer;
if(pDb){
if(this.onclose && (this.onclose.before instanceof Function)){ if(this.onclose && (this.onclose.before instanceof Function)){
try{this.onclose.before(this)} try{this.onclose.before(this)}
catch(e){/*ignore*/} catch(e){/*ignore*/}
} }
const pDb = this.pointer;
Object.keys(__stmtMap.get(this)).forEach((k,s)=>{ Object.keys(__stmtMap.get(this)).forEach((k,s)=>{
if(s && s.pointer){ if(s && s.pointer){
try{s.finalize()} try{s.finalize()}
@ -737,7 +774,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}); });
__ptrMap.delete(this); __ptrMap.delete(this);
__stmtMap.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)){ if(this.onclose && (this.onclose.after instanceof Function)){
try{this.onclose.after(this)} try{this.onclose.after(this)}
catch(e){/*ignore*/} catch(e){/*ignore*/}
@ -1450,9 +1489,63 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
*/ */
checkRc: function(resultCode){ checkRc: function(resultCode){
return checkSqlite3Rc(this, resultCode); return checkSqlite3Rc(this, resultCode);
} },
}/*DB.prototype*/; }/*DB.prototype*/;
/**
Returns a new oo1.DB instance which wraps the given (sqlite3*)
WASM pointer, optionally with or without taking over ownership of
that pointer.
The first argument must be either a non-NULL (sqlite3*) WASM
pointer.
The second argument, defaulting to false, specifies ownership of
the first argument. If it is truthy, the returned object will
pass that pointer to sqlite3_close() when its close() method is
called, otherwise it will not.
Throws if pDb is not a non-0 WASM pointer.
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 unless this function is
passed a thruthy second argument.
To stress:
- DO NOT call sqlite3_close() (or similar) on the being-proxied
pointer while a proxy is active.
- ALWAYS eventually call close() on the returned object. If the
proxy does not own the underlying handle then its MUST be
closed BEFORE the being-proxied handle is closed.
Design notes:
- wrapHandle() "could" accept a DB object instance as its first
argument and proxy thatDb.pointer but there is currently no use
case where doing so would be useful, so it does not allow
that. That restriction may be lifted in a future version.
*/
DB.wrapHandle = function(pDb, takeOwnership=false){
if( !pDb || !wasm.isPtr(pDb) ){
throw new sqlite3.SQLite3Error(capi.SQLITE_MISUSE,
"Argument must be a WASM sqlite3 pointer");
}
return new DB({
/* This ctor call style is very specifically internal-use-only.
It is not documented and may change at any time. */
"sqlite3*": pDb,
"sqlite3*:takeOwnership": !!takeOwnership
});
};
/** Throws if the given Stmt has been finalized, else stmt is /** Throws if the given Stmt has been finalized, else stmt is
returned. */ returned. */
@ -1474,8 +1567,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
case BindTypes.string: case BindTypes.string:
return t; return t;
case BindTypes.bigint: case BindTypes.bigint:
if(wasm.bigIntEnabled) return t; return wasm.bigIntEnabled ? t : undefined;
/* else fall through */
default: default:
return util.isBindableTypedArray(v) ? BindTypes.blob : undefined; return util.isBindableTypedArray(v) ? BindTypes.blob : undefined;
} }
@ -1641,12 +1733,19 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
This method always throws if called when it is illegal to do This method always throws if called when it is illegal to do
so. Namely, when triggered via a per-row callback handler of a so. Namely, when triggered via a per-row callback handler of a
DB.exec() call. 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(){ finalize: function(){
if(this.pointer){ const ptr = this.pointer;
if(ptr){
affirmNotLockedByExec(this,'finalize()'); affirmNotLockedByExec(this,'finalize()');
const rc = capi.sqlite3_finalize(this.pointer); const rc = (__doesNotOwnHandle.delete(this)
delete __stmtMap.get(this.db)[this.pointer]; ? 0
: capi.sqlite3_finalize(ptr));
delete __stmtMap.get(this.db)[ptr];
__ptrMap.delete(this); __ptrMap.delete(this);
__execLock.delete(this); __execLock.delete(this);
__stmtMayGet.delete(this); __stmtMayGet.delete(this);
@ -2134,6 +2233,64 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
set: ()=>toss3("The columnCount property is read-only.") 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,
optionally 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, defaulting to false, 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 pStmt is unchanged and pStmt 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*). Because
DB.wrapHandle() enables multiple DB objects to proxy the same
(sqlite3*), we cannot unambiguously translate the first arugment
from (sqlite3*) to DB instances for us with this function's first
argument.
*/
Stmt.wrapHandle = function(oo1db, pStmt, 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( !pStmt || !wasm.isPtr(pStmt) ){
throw new sqlite3.SQLite3Error(sqlite3.SQLITE_MISUSE,
"Second argument must be a WASM "+
"sqlite3_stmt pointer");
}
return new Stmt(oo1db, pStmt, BindTypes, !!takeOwnership);
}
/** The OO API's public namespace. */ /** The OO API's public namespace. */
sqlite3.oo1 = { sqlite3.oo1 = {
DB, DB,

View File

@ -41,7 +41,7 @@
ES6 worker module build: 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 //#if target=es6-module
import {default as sqlite3InitModule} from './jswasm/sqlite3.mjs'; import {default as sqlite3InitModule} from './jswasm/sqlite3.mjs';
@ -221,7 +221,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
else if(filter instanceof Function) pass = filter(err); else if(filter instanceof Function) pass = filter(err);
else if('string' === typeof filter) pass = (err.message === filter); else if('string' === typeof filter) pass = (err.message === filter);
if(!pass){ if(!pass){
throw new Error(msg || ("Filter rejected this exception: "+err.message)); throw new Error(msg || ("Filter rejected this exception: <<"+err.message+">>"));
} }
return this; return this;
}, },
@ -1209,6 +1209,104 @@ 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);
const misuseMsg = "SQLITE_MISUSE: Argument must be a WASM sqlite3 pointer";
T.mustThrowMatching(()=>sqlite3.oo1.DB.wrapHandle(this.db), misuseMsg)
.mustThrowMatching(()=>sqlite3.oo1.DB.wrapHandle(0), misuseMsg);
let dw = sqlite3.oo1.DB.wrapHandle(this.db.pointer);
//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();
const select1 = "select 1";
rc = capi.sqlite3_prepare_v2( dw, select1, -1, ppOut, 0 );
T.assert( 0===rc, 'prepare_v2() rc='+rc );
pStmt = wasm.peekPtr(ppOut);
T.assert( pStmt && wasm.isPtr(pStmt), 'pStmt is valid?' );
try {
//log( "capi.sqlite3_sql() =",capi.sqlite3_sql(pStmt));
T.assert( select1 === capi.sqlite3_sql(pStmt), 'SQL mismatch' );
q = sqlite3.oo1.Stmt.wrapHandle(dw, pStmt, false);
//log("q@"+pStmt+" does not own handle");
T.assert( q.step(), "step()" )
.assert( !q.step(), "!step()" );
q.finalize();
q = undefined;
T.assert( select1 === capi.sqlite3_sql(pStmt), 'SQL mismatch'
/* This will fail if we've mismanaged pStmt's lifetime */);
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){ .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); let rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, 0, 0);
@ -1268,6 +1366,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
/columnCount property is read-only/) /columnCount property is read-only/)
.assert(1===st.columnCount) .assert(1===st.columnCount)
.assert(0===st.parameterCount) .assert(0===st.parameterCount)
.assert(0===capi.sqlite3_bind_parameter_count(st))
.mustThrow(()=>st.bind(1,null)) .mustThrow(()=>st.bind(1,null))
.assert(true===st.step()) .assert(true===st.step())
.assert(3 === st.get(0)) .assert(3 === st.get(0))
@ -1490,6 +1589,8 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
let st = db.prepare("update t set b=:b where a='blob'"); let st = db.prepare("update t set b=:b where a='blob'");
try { try {
T.assert(0===st.columnCount) T.assert(0===st.columnCount)
.assert(1===st.parameterCount)
.assert(1===capi.sqlite3_bind_parameter_count(st))
.assert( false===st.isReadOnly() ); .assert( false===st.isReadOnly() );
const ndx = st.getParamIndex(':b'); const ndx = st.getParamIndex(':b');
T.assert(1===ndx); T.assert(1===ndx);
@ -3329,6 +3430,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
db.exec("create table t(a)"); db.exec("create table t(a)");
const stmt = db.prepare("insert into t(a) values($a)"); const stmt = db.prepare("insert into t(a) values($a)");
T.assert( 1===capi.sqlite3_bind_parameter_count(stmt) ) T.assert( 1===capi.sqlite3_bind_parameter_count(stmt) )
.assert( 1===stmt.parameterCount )
.assert( 1===capi.sqlite3_bind_parameter_index(stmt, "$a") ) .assert( 1===capi.sqlite3_bind_parameter_index(stmt, "$a") )
.assert( 0===capi.sqlite3_bind_parameter_index(stmt, ":a") ) .assert( 0===capi.sqlite3_bind_parameter_index(stmt, ":a") )
.assert( 1===stmt.getParamIndex("$a") ) .assert( 1===stmt.getParamIndex("$a") )

View File

@ -1,5 +1,5 @@
C Additional\sheader\scomment\sdocumentation\sin\sthe\sext/misc/vtablog.c\stest\sextension. C wasm:\sintroduce\sthe\ssqlite3.oo1.DB.wrapHandle()\sand\sStmt.wrapHandle()\sAPIs,\swhich\senable\sclients\sto\swrap\s(sqlite3*)\sresp.\s(sqlite3_stmt*)\spointers\sin\stheir\soo1\sAPI\scounterparts,\soptionally\swith\sor\swithout\staking\sover\sownership\sof\sthe\spointer.
D 2025-07-11T17:02:11.169 D 2025-07-11T19:52:36.729
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@ -621,7 +621,7 @@ F ext/session/sqlite3session.c 6b0877fe1ab832aa4b85eaca72606dfd1630a1363a1be7af1
F ext/session/sqlite3session.h 9bb1a6687b467764b35178dc29bbd2c57ab8cd3acdc8a62f088c34ad17e4fe2b F ext/session/sqlite3session.h 9bb1a6687b467764b35178dc29bbd2c57ab8cd3acdc8a62f088c34ad17e4fe2b
F ext/session/test_session.c 2ddff73ea368d827028c32851b291416e1008845832feb27b751d15e57e13cc3 F ext/session/test_session.c 2ddff73ea368d827028c32851b291416e1008845832feb27b751d15e57e13cc3
F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c
F ext/wasm/GNUmakefile fa694fe78fc0acfd4406a36133ce4aff8820e086f4dd5f1473d2fafce590ab64 F ext/wasm/GNUmakefile d62af1b0914eb2e03fa6e4e75e93acadc8f4faeb2d56335da25d61b9ea144c53
F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a
F ext/wasm/README.md b89605f65661cf35bf034ff6d43e448cc169b8017fc105d498e33b81218b482c F ext/wasm/README.md b89605f65661cf35bf034ff6d43e448cc169b8017fc105d498e33b81218b482c
F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff
@ -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/pre-js.c-pp.js a614a2c82b12c4d96d8e3ba77330329efc53c4d56a8a7e60ade900f341866cfb
F ext/wasm/api/sqlite3-api-cleanup.js 3ac1786e461ada63033143be8c3b00b26b939540661f3e839515bb92f2e35359 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-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 f59e59f0d94ba5835c6b7fc9b800a4aa5084e1224721a07e3cd6cc7fef1789c2
F ext/wasm/api/sqlite3-api-prologue.js 4f1c2a9dc9caf631907766e9872c27d11b255ccae779e8af01c7f8b932817214 F ext/wasm/api/sqlite3-api-prologue.js 4f1c2a9dc9caf631907766e9872c27d11b255ccae779e8af01c7f8b932817214
F ext/wasm/api/sqlite3-api-worker1.c-pp.js f646a65257973b8c4481f8a6a216370b85644f23e64b126e7ae113570587c0ab F ext/wasm/api/sqlite3-api-worker1.c-pp.js f646a65257973b8c4481f8a6a216370b85644f23e64b126e7ae113570587c0ab
F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 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/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c
F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c
F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2 F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2
F ext/wasm/tester1.c-pp.js 766a2ba51a2619d41a49be7c6a1ad014c1d23fc97b67496e4f103038203eb17d F ext/wasm/tester1.c-pp.js 0abba4bd54f6b22adaadf836c04d3163399f7a8a490fd60f20daac5f9c42b47d
F ext/wasm/tests/opfs/concurrency/index.html 657578a6e9ce1e9b8be951549ed93a6a471f4520a99e5b545928668f4285fb5e F ext/wasm/tests/opfs/concurrency/index.html 657578a6e9ce1e9b8be951549ed93a6a471f4520a99e5b545928668f4285fb5e
F ext/wasm/tests/opfs/concurrency/test.js d08889a5bb6e61937d0b8cbb78c9efbefbf65ad09f510589c779b7cc6a803a88 F ext/wasm/tests/opfs/concurrency/test.js d08889a5bb6e61937d0b8cbb78c9efbefbf65ad09f510589c779b7cc6a803a88
F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@ -2212,8 +2212,9 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350
F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P 9d68971c58261bce72b49c574cf07ad31add62bee814c58840b927fed7eb87b1 P 3656acfaa3011321a6e17fb81e5bdedcfffeab6035f133ab89ae9589bf5bef72 53401b5435e30c4b47b6e203976b714d616246d734b5876a34f53f6388f872f8
R 5643c35e4f11fb6cf54743a0885fc1f7 R 6e106d1cb56322541040e2b7c468c9ad
U drh T +closed 53401b5435e30c4b47b6e203976b714d616246d734b5876a34f53f6388f872f8 Closed\sby\sintegrate-merge.
Z b5fe84fd8bb20fd9110a384dcfcde9c2 U stephan
Z f91aebcbb3981855ba9f714a42d1af4f
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
3656acfaa3011321a6e17fb81e5bdedcfffeab6035f133ab89ae9589bf5bef72 e5d079549594ca44852773b8919894866394e47ad725dadc7f65242413a219d3