Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,21 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
this.sqlPtr = null;
};

/** Free the resources held by this iterator, in case it will not be run
* to completion. This is called automatically when a for..of loop exits
* early (e.g. via break, return, or an exception), and can also be called
* manually.
@return {StatementIterator.StatementIteratorResult}
*/
StatementIterator.prototype["return"] = function () {
if (this.activeStatement !== null) {
this.activeStatement.free();
this.activeStatement = null;
}
this.finalize();
return { done: true };
};

/** Get any un-executed portions remaining of the original SQL string
@return {String}
*/
Expand Down
39 changes: 39 additions & 0 deletions test/test_statement_iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,45 @@ exports.test = function (SQL, assert) {
}
assert.equal(count, 3, "For loop iterates correctly");

// Exiting a for...of loop early frees the statement it yielded (issue #457).
// The statement is what leaks: finalizing only the iterator leaves it
// registered on the database until close(), so each case below asserts the
// statement is gone and not merely that the iterator stopped.
var earlyExit = db.iterateStatements("SELECT 1 AS x; SELECT 2 AS y");
var brokeOn = null;
for (let statement of earlyExit) {
brokeOn = statement;
statement.step();
break;
}
assert.throws(function () { brokeOn.step(); }, /Statement closed/,
"Statement yielded before a break is freed");
assert.deepEqual(earlyExit.next(), { done: true },
"Iterator done after for...of loop is exited via break");

var throwing = db.iterateStatements("SELECT 1 AS x; SELECT 2 AS y");
var threwOn = null;
try {
for (let statement of throwing) {
threwOn = statement;
throw new Error("early exit");
}
} catch (e) { /* expected */ }
assert.throws(function () { threwOn.step(); }, /Statement closed/,
"Statement yielded before a throw is freed");
assert.deepEqual(throwing.next(), { done: true },
"Iterator done after for...of loop body throws");

// return() can also be called manually to free the iterator early
var manual = db.iterateStatements("SELECT 1 AS x; SELECT 2 AS y");
var manualStatement = manual.next().value;
assert.deepEqual(manual["return"](), { done: true },
"return() reports done when called manually");
assert.throws(function () { manualStatement.step(); }, /Statement closed/,
"Statement is freed when return() is called manually");
assert.deepEqual(manual.next(), { done: true },
"Iterator done after calling return()");

var badsql = "SELECT 1 as x;garbage in, garbage out";

// bad sql will stop iteration
Expand Down