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

oo1.Stmt.finalize() no longer throws, but instead returns the same as the C API. oo1.DB.exec() now triggers the INSERT...RETURNING locking failure as an exception via reset() instead of finalize(). Some code-adjacent internal API renaming for clarity's sake.

FossilOrigin-Name: 4ee6b3aa531b980acea4c4b58ee256e765c5105100468928def3d4c9825fa9bc
This commit is contained in:
stephan
2023-05-19 17:50:16 +00:00
parent fe19414331
commit a382d236da
4 changed files with 64 additions and 64 deletions

View File

@ -554,7 +554,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
} }
const pDb = this.pointer; const pDb = this.pointer;
Object.keys(__stmtMap.get(this)).forEach((k,s)=>{ Object.keys(__stmtMap.get(this)).forEach((k,s)=>{
if(s && s.pointer) s.finalize(); if(s && s.pointer){
try{s.finalize()}
catch(e){/*ignore*/}
}
}); });
__ptrMap.delete(this); __ptrMap.delete(this);
__stmtMap.delete(this); __stmtMap.delete(this);
@ -878,16 +881,16 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
and names. */) ? 0 : 1; and names. */) ? 0 : 1;
evalFirstResult = false; evalFirstResult = false;
if(arg.cbArg || resultRows){ if(arg.cbArg || resultRows){
for(; stmt.step(); stmt._isLocked = false){ for(; stmt.step(); stmt._lockedByExec = false){
if(0===gotColNames++) stmt.getColumnNames(opt.columnNames); if(0===gotColNames++) stmt.getColumnNames(opt.columnNames);
stmt._isLocked = true; stmt._lockedByExec = true;
const row = arg.cbArg(stmt); const row = arg.cbArg(stmt);
if(resultRows) resultRows.push(row); if(resultRows) resultRows.push(row);
if(callback && false === callback.call(opt, row, stmt)){ if(callback && false === callback.call(opt, row, stmt)){
break; break;
} }
} }
stmt._isLocked = false; stmt._lockedByExec = false;
} }
if(0===gotColNames){ if(0===gotColNames){
/* opt.columnNames was provided but we visited no result rows */ /* opt.columnNames was provided but we visited no result rows */
@ -896,16 +899,20 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}else{ }else{
stmt.step(); stmt.step();
} }
stmt.finalize(); stmt.reset(
/* In order to trigger an exception in the
INSERT...RETURNING locking scenario:
https://sqlite.org/forum/forumpost/36f7a2e7494897df
*/).finalize();
stmt = null; stmt = null;
} }/*prepare() loop*/
}/*catch(e){ }/*catch(e){
sqlite3.config.warn("DB.exec() is propagating exception",opt,e); sqlite3.config.warn("DB.exec() is propagating exception",opt,e);
throw e; throw e;
}*/finally{ }*/finally{
wasm.scopedAllocPop(stack); wasm.scopedAllocPop(stack);
if(stmt){ if(stmt){
delete stmt._isLocked; delete stmt._lockedByExec;
stmt.finalize(); stmt.finalize();
} }
} }
@ -1321,15 +1328,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}; };
/** /**
If stmt._isLocked is truthy, this throws an exception If stmt._lockedByExec is truthy, this throws an exception
complaining that the 2nd argument (an operation name, complaining that the 2nd argument (an operation name,
e.g. "bind()") is not legal while the statement is "locked". e.g. "bind()") is not legal while the statement is "locked".
Locking happens before an exec()-like callback is passed a Locking happens before an exec()-like callback is passed a
statement, to ensure that the callback does not mutate or statement, to ensure that the callback does not mutate or
finalize the statement. If it does not throw, it returns stmt. finalize the statement. If it does not throw, it returns stmt.
*/ */
const affirmUnlocked = function(stmt,currentOpName){ const affirmNotLockedByExec = function(stmt,currentOpName){
if(stmt._isLocked){ if(stmt._lockedByExec){
toss3("Operation is illegal when statement is locked:",currentOpName); toss3("Operation is illegal when statement is locked:",currentOpName);
} }
return stmt; return stmt;
@ -1342,14 +1349,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
success. success.
*/ */
const bindOne = function f(stmt,ndx,bindType,val){ const bindOne = function f(stmt,ndx,bindType,val){
affirmUnlocked(affirmStmtOpen(stmt), 'bind()'); affirmNotLockedByExec(affirmStmtOpen(stmt), 'bind()');
if(!f._){ if(!f._){
f._tooBigInt = (v)=>toss3( f._tooBigInt = (v)=>toss3(
"BigInt value is too big to store without precision loss:", v "BigInt value is too big to store without precision loss:", v
); );
/* Reminder: when not in BigInt mode, it's impossible for
JS to represent a number out of the range we can bind,
so we have no range checking. */
f._ = { f._ = {
string: function(stmt, ndx, val, asBlob){ string: function(stmt, ndx, val, asBlob){
const [pStr, n] = wasm.allocCString(val, true); const [pStr, n] = wasm.allocCString(val, true);
@ -1423,33 +1427,28 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Stmt.prototype = { Stmt.prototype = {
/** /**
"Finalizes" this statement. This is a no-op if the "Finalizes" this statement. This is a no-op if the statement
statement has already been finalized. Returns has already been finalized. Returns the result of
undefined. Most methods in this class will throw if called sqlite3_finalize() (0 on success, non-0 on error), or the
after this is. undefined value if the statement has already been
finalized. Regardless of success or failure, most methods in
this class will throw if called after this is.
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, e.g. from a per-row callback handler of a DB.exec() call. so. Namely, when triggered via a per-row callback handler of a
DB.exec() call.
As of versions 3.42.1 and 3.43, this method will throw by
default if sqlite3_finalize() returns an error code, which can
happen in certain unusual cases involving locking. When it
throws for this reason, throwing is delayed until after all
resources are cleaned up. That is, the finalization still runs
to completion.
*/ */
finalize: function(){ finalize: function(){
if(this.pointer){ if(this.pointer){
affirmUnlocked(this,'finalize()'); affirmNotLockedByExec(this,'finalize()');
const rc = capi.sqlite3_finalize(this.pointer), const rc = capi.sqlite3_finalize(this.pointer);
db = this.db; delete __stmtMap.get(this.db)[this.pointer];
delete __stmtMap.get(db)[this.pointer];
__ptrMap.delete(this); __ptrMap.delete(this);
delete this._mayGet; delete this._mayGet;
delete this.parameterCount; delete this.parameterCount;
delete this._isLocked; delete this._lockedByExec;
delete this.db; delete this.db;
checkSqlite3Rc(db, rc); return rc;
} }
}, },
/** /**
@ -1459,7 +1458,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
a DB.exec() call). a DB.exec() call).
*/ */
clearBindings: function(){ clearBindings: function(){
affirmUnlocked(affirmStmtOpen(this), 'clearBindings()') affirmNotLockedByExec(affirmStmtOpen(this), 'clearBindings()')
capi.sqlite3_clear_bindings(this.pointer); capi.sqlite3_clear_bindings(this.pointer);
this._mayGet = false; this._mayGet = false;
return this; return this;
@ -1467,19 +1466,24 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/** /**
Resets this statement so that it may be step()ed again from the Resets this statement so that it may be step()ed again from the
beginning. Returns this object. Throws if this statement has beginning. Returns this object. Throws if this statement has
been finalized or if it may not legally be reset because it is been finalized, if it may not legally be reset because it is
currently being used from a DB.exec() callback. currently being used from a DB.exec() callback, or if the
underlying call to sqlite3_reset() returns non-0.
If passed a truthy argument then this.clearBindings() is If passed a truthy argument then this.clearBindings() is
also called, otherwise any existing bindings, along with also called, otherwise any existing bindings, along with
any memory allocated for them, are retained. any memory allocated for them, are retained.
As of versions 3.42.1 and 3.43, this function throws if the In verions 3.42.0 and earlier, this function did not throw if
underlying call to sqlite3_reset() returns non-0. That is sqlite3_reset() returns non-0, but it was discovered that
necessary for catching errors in certain locking-related cases. throwing (or significant extra client-side code) is necessary
in order to avoid certain silent failure scenarios, as
discussed at:
https://sqlite.org/forum/forumpost/36f7a2e7494897df
*/ */
reset: function(alsoClearBinds){ reset: function(alsoClearBinds){
affirmUnlocked(this,'reset()'); affirmNotLockedByExec(this,'reset()');
if(alsoClearBinds) this.clearBindings(); if(alsoClearBinds) this.clearBindings();
const rc = capi.sqlite3_reset(affirmStmtOpen(this).pointer); const rc = capi.sqlite3_reset(affirmStmtOpen(this).pointer);
this._mayGet = false; this._mayGet = false;
@ -1632,7 +1636,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
value is returned. Throws on error. value is returned. Throws on error.
*/ */
step: function(){ step: function(){
affirmUnlocked(this, 'step()'); affirmNotLockedByExec(this, 'step()');
const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer); const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer);
switch(rc){ switch(rc){
case capi.SQLITE_DONE: return this._mayGet = false; case capi.SQLITE_DONE: return this._mayGet = false;
@ -1681,20 +1685,12 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
``` ```
*/ */
stepFinalize: function(){ stepFinalize: function(){
let rc, err;
try{ try{
rc = this.step(); return this.step();
}catch(e){ }finally{
err = e;
}
if(err){
try{this.finalize()} try{this.finalize()}
catch(x){/*ignored*/} catch(e){/*ignored*/}
throw err;
}else{
this.finalize();
} }
return rc;
}, },
/** /**
Fetches the value from the given 0-based column index of Fetches the value from the given 0-based column index of

View File

@ -1220,6 +1220,7 @@ self.sqlite3InitModule = sqlite3InitModule;
); );
//debug("statement =",st); //debug("statement =",st);
this.progressHandlerCount = 0; this.progressHandlerCount = 0;
let rc;
try { try {
T.assert(wasm.isPtr(st.pointer)) T.assert(wasm.isPtr(st.pointer))
.mustThrowMatching(()=>st.pointer=1, /read-only/) .mustThrowMatching(()=>st.pointer=1, /read-only/)
@ -1267,10 +1268,11 @@ self.sqlite3InitModule = sqlite3InitModule;
assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)). assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0)); assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
}finally{ }finally{
st.finalize(); rc = st.finalize();
} }
T.assert(!st.pointer) T.assert(!st.pointer)
.assert(0===this.db.openStatementCount()); .assert(0===this.db.openStatementCount())
.assert(0===rc);
T.mustThrowMatching(()=>new sqlite3.oo1.Stmt("hi"), function(err){ T.mustThrowMatching(()=>new sqlite3.oo1.Stmt("hi"), function(err){
return (err instanceof sqlite3.SQLite3Error) return (err instanceof sqlite3.SQLite3Error)
@ -1460,9 +1462,11 @@ self.sqlite3InitModule = sqlite3InitModule;
T.assert(0===st.columnCount); T.assert(0===st.columnCount);
const ndx = st.getParamIndex(':b'); const ndx = st.getParamIndex(':b');
T.assert(1===ndx); T.assert(1===ndx);
st.bindAsBlob(ndx, "ima blob").reset(true); st.bindAsBlob(ndx, "ima blob")
/*step() skipped intentionally*/.reset(true);
} finally { } finally {
st.finalize(); T.assert(0===st.finalize())
.assert(undefined===st.finalize());
} }
try { try {

View File

@ -1,5 +1,5 @@
C Improved\sdocumentation\sfor\ssqlite3_reset(),\sin\sresponse\sto\n[forum:/forumpost/a72bab3dea|forum\spost\sa72bab3dea]. C oo1.Stmt.finalize()\sno\slonger\sthrows,\sbut\sinstead\sreturns\sthe\ssame\sas\sthe\sC\sAPI.\soo1.DB.exec()\snow\striggers\sthe\sINSERT...RETURNING\slocking\sfailure\sas\san\sexception\svia\sreset()\sinstead\sof\sfinalize().\sSome\scode-adjacent\sinternal\sAPI\srenaming\sfor\sclarity's\ssake.
D 2023-05-19T16:42:49.388 D 2023-05-19T17:50:16.695
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -494,7 +494,7 @@ F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b
F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057afb08161d7511219 F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057afb08161d7511219
F ext/wasm/api/sqlite3-api-cleanup.js cc21e3486da748463e02bbe51e2464c6ac136587cdfd5aa00cd0b5385f6ca808 F ext/wasm/api/sqlite3-api-cleanup.js cc21e3486da748463e02bbe51e2464c6ac136587cdfd5aa00cd0b5385f6ca808
F ext/wasm/api/sqlite3-api-glue.js f1b2dcb944de5138bb5bd9a1559d2e76a4f3ec25260963d709e8237476688803 F ext/wasm/api/sqlite3-api-glue.js f1b2dcb944de5138bb5bd9a1559d2e76a4f3ec25260963d709e8237476688803
F ext/wasm/api/sqlite3-api-oo1.js ac488a0e04d8b247f6cbc1b13b6ba54fac52342a80a00e1d6b17eab12f46aa59 F ext/wasm/api/sqlite3-api-oo1.js c6a258fce7d833d48220d7e4a18512fa16f79e501e4fd371e280cdab842cd4c0
F ext/wasm/api/sqlite3-api-prologue.js 17f4ec398ba34c5c666fea8e8c4eb82064a35b302f2f2eb355283cd8d3f68ed5 F ext/wasm/api/sqlite3-api-prologue.js 17f4ec398ba34c5c666fea8e8c4eb82064a35b302f2f2eb355283cd8d3f68ed5
F ext/wasm/api/sqlite3-api-worker1.js 40a5b1813fcbe789f23ae196c833432c8c83e7054d660194ddfc51eab1c5b9bf F ext/wasm/api/sqlite3-api-worker1.js 40a5b1813fcbe789f23ae196c833432c8c83e7054d660194ddfc51eab1c5b9bf
F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89
@ -546,7 +546,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b
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 8f3507b22f829818a37fc44ffa81e84b4192a2ebde6a351d14b9009e136883d9 F ext/wasm/tester1.c-pp.js 6722037cdafb25ad0415f4561bd216427b36a45ff9f51096c370e374725ba3a9
F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1 F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1
F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d
F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@ -2070,8 +2070,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 487ae12c9a21e5862bd590bbb1030c39734657d52136cf67b98c7545e6ecbe1c P 1dfeb3dceee8f30daf5462683f264b9de23e7068e036e70b11ee1b608ac2f7fa
R 39b64ba449418cb0ab801f0a9bec4df8 R a12ad3984ab6086345836bc1a346ae92
U drh U stephan
Z 46fed3a751762448fe0476ef3a96f8d1 Z a9829d02233e35008022e5ee0b47f59f
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
1dfeb3dceee8f30daf5462683f264b9de23e7068e036e70b11ee1b608ac2f7fa 4ee6b3aa531b980acea4c4b58ee256e765c5105100468928def3d4c9825fa9bc