From 4d453cc2eda12164bb14ab9e773101919f85aac7 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 6 Jul 2025 15:01:44 +0000 Subject: 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 --- ext/wasm/api/sqlite3-api-oo1.c-pp.js | 321 +++++++++++++++++++++++++++-------- 1 file changed, 246 insertions(+), 75 deletions(-) (limited to 'ext/wasm/api/sqlite3-api-oo1.c-pp.js') diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js index 06f916002..91d8bc544 100644 --- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js @@ -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, -- cgit v1.2.3 From 787f4c3f09d00e79c04822a99fd9a03f7878e959 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 6 Jul 2025 15:13:06 +0000 Subject: JS doc additions. FossilOrigin-Name: 7933505f0c1d25c6ee81ead8edc5ea649a84ec0582321bcf5ed4a17232750f90 --- ext/wasm/api/sqlite3-api-oo1.c-pp.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) (limited to 'ext/wasm/api/sqlite3-api-oo1.c-pp.js') diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js index 91d8bc544..13ca9a4bd 100644 --- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js @@ -533,7 +533,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } this.db = arguments[0]; __ptrMap.set(this, arguments[1]); - if( arguments.length>3 && false===arguments[3] ){ + if( arguments.length>3 && !!arguments[3] ){ __doesNotOwnHandle.add(this); } }; @@ -728,10 +728,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }, /** Finalizes all open statements and closes this database - connection. This is a no-op if the db has already been - closed. After calling close(), `this.pointer` will resolve to - `undefined`, so that can be used to check whether the db - instance is still opened. + connection (with one exception noted below). This is a no-op if + the db has already been closed. After calling close(), + `this.pointer` will resolve to `undefined`, and that can be + used to check whether the db instance is still opened. If this.onclose.before is a function then it is called before any close-related cleanup. @@ -751,6 +751,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ all, will never trigger close(), so onclose handlers are not a reliable way to implement close-time cleanup or maintenance of 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(){ const pDb = this.pointer; @@ -1491,10 +1496,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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. + The second argument, defaulting to false, 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 @@ -2267,12 +2272,12 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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. + 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 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 -- cgit v1.2.3 From 240319decde8af8fc4d0896d7138c7ca282627ea Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 6 Jul 2025 15:36:28 +0000 Subject: Remove some dead JS code and some extraneous debug output from test code. FossilOrigin-Name: 6e73770a7f3845055e0130012d844c32c4a1bfdb87e8379c161e1a266a808143 --- ext/wasm/api/sqlite3-api-oo1.c-pp.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'ext/wasm/api/sqlite3-api-oo1.c-pp.js') diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js index 13ca9a4bd..cc4c9bb28 100644 --- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js @@ -429,7 +429,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - `vfs`: the VFS fname //#if enable-see - SEE-capable builds optionally support ONE of the following additional options: @@ -455,7 +454,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ is supplied and the database is encrypted, execution of the post-initialization SQL will fail, causing the constructor to throw. - //#endif enable-see The `filename` and `vfs` arguments may be either JS strings or @@ -483,8 +481,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** Internal-use enum for mapping JS types to DB-bindable types. These do not (and need not) line up with the SQLITE_type - values. All values in this enum must be truthy and distinct - but they need not be numbers. + values. All values in this enum must be truthy and (mostly) + distinct but they need not be numbers. */ const BindTypes = { null: 1, @@ -493,7 +491,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ boolean: 4, blob: 5 }; - BindTypes['undefined'] == BindTypes.null; if(wasm.bigIntEnabled){ BindTypes.bigint = BindTypes.number; } @@ -1589,8 +1586,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ case BindTypes.string: return t; case BindTypes.bigint: - if(wasm.bigIntEnabled) return t; - /* else fall through */ + return wasm.bigIntEnabled ? t : undefined; default: return util.isBindableTypedArray(v) ? BindTypes.blob : undefined; } -- cgit v1.2.3 From 2ba5a93e8f6f42e617fad7b840d0720bb1dfbe26 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Jul 2025 13:43:53 +0000 Subject: wasm: DB.wrapHandle() no longer accepts a DB object as its first argument, as there's no apparent use case for proxying one DB object with another. Doc improvements for the new code. FossilOrigin-Name: 0ee19db02a9b9320681d4393d9ba52889d149eac8ee114fc7b7f52a57271c139 --- ext/wasm/api/sqlite3-api-oo1.c-pp.js | 121 +++++++++++++++-------------------- 1 file changed, 52 insertions(+), 69 deletions(-) (limited to 'ext/wasm/api/sqlite3-api-oo1.c-pp.js') diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js index cc4c9bb28..27176c026 100644 --- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js @@ -38,13 +38,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ 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. + A Set of oo1.DB or oo1.Stmt objects which are proxies for + (sqlite3*) resp. (sqlite3_stmt*) pointers. Such objects + optionally 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. + + See DB.wrapHandle() and Stmt.wrapHandle(). */ const __doesNotOwnHandle = new Set(); /** @@ -1488,22 +1490,17 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/*DB.prototype*/; /** - Returns a new oo1.DB instance which wraps the given db. + 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 or a non-close()d instance of oo1.DB. - - The second argument, defaulting to false, 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. + pointer. - 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(). + 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 db cannot be resolved to one of the legal options. @@ -1515,56 +1512,38 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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. + actually close the proxied db handle unless this function is + passed a thruthy second argument. 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.) + - DO NOT call sqlite3_close() (or similar) on the being-proxied + instance 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(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"); + 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"); } - const dc = new ctor({ - "sqlite3*": ptr, + 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 }); - 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. */ @@ -2260,8 +2239,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** 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. + 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]. @@ -2272,7 +2251,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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 + 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 @@ -2289,21 +2268,25 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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*). + 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, stmtPtr, takeOwnership=false){ + 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( !stmtPtr || !wasm.isPtr(stmtPtr) ){ + 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, stmtPtr, BindTypes, !!takeOwnership); + return new Stmt(oo1db, pStmt, BindTypes, !!takeOwnership); } /** The OO API's public namespace. */ -- cgit v1.2.3 From 6fd9469a3281ae67929bfc518d0e80674ec15378 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Jul 2025 09:25:57 +0000 Subject: Minor JS doc tweaks. FossilOrigin-Name: 34d893b4837be8bb679e9809befd98201dd79c8d2054f41429b18793b77e840f --- ext/wasm/api/sqlite3-api-oo1.c-pp.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'ext/wasm/api/sqlite3-api-oo1.c-pp.js') diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js index 27176c026..1473e4e45 100644 --- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js @@ -1502,7 +1502,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ pass that pointer to sqlite3_close() when its close() method is called, otherwise it will not. - Throws if db cannot be resolved to one of the legal options. + 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, @@ -1510,16 +1510,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 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 + 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. - The following quirks and requirements apply when proxying another - DB instance, as opposed to a (sqlite3*): + To stress: - DO NOT call sqlite3_close() (or similar) on the being-proxied - instance while a proxy is active. + 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 -- cgit v1.2.3 From c493e4516aa4b45f98c29e546bb424974839846a Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 11 Jul 2025 19:34:30 +0000 Subject: Correct a one-too-many-exclamation-points bug which caused the opposite of intended semantics in oo1.Stmt.pointer ownership. FossilOrigin-Name: 91e709f36d36174534b4cf4ff548a558e66cca5e23a6c8c106bc43375fc3ce72 --- ext/wasm/api/sqlite3-api-oo1.c-pp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/wasm/api/sqlite3-api-oo1.c-pp.js') diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js index 1473e4e45..f92dde48a 100644 --- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js @@ -532,7 +532,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } this.db = arguments[0]; __ptrMap.set(this, arguments[1]); - if( arguments.length>3 && !!arguments[3] ){ + if( arguments.length>3 && !arguments[3] ){ __doesNotOwnHandle.add(this); } }; -- cgit v1.2.3 From 1f68085849b247a6a1773603cd56d915a1c5ce92 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 11 Jul 2025 19:44:42 +0000 Subject: Clearer docs for the unowned db/stmt handle mechanism. FossilOrigin-Name: 53401b5435e30c4b47b6e203976b714d616246d734b5876a34f53f6388f872f8 --- ext/wasm/api/sqlite3-api-oo1.c-pp.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'ext/wasm/api/sqlite3-api-oo1.c-pp.js') diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js index f92dde48a..62c44fa9d 100644 --- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js @@ -39,12 +39,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const __ptrMap = new WeakMap(); /** A Set of oo1.DB or oo1.Stmt objects which are proxies for - (sqlite3*) resp. (sqlite3_stmt*) pointers. Such objects - optionally 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. + (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(). */ -- cgit v1.2.3