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
23 changes: 21 additions & 2 deletions src/sqlite-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -493,10 +493,29 @@ export function Factory(Module) {
const rc = await retry(() => f(zFilename, tmpPtr[0], flags, zVfs));

const db = Module.getValue(tmpPtr[0], '*');
databases.add(db);
if (rc !== SQLite.SQLITE_OK) {
// sqlite3_open_v2 usually returns a database handle even when opening fails.
// Capture its error before closing because close may replace the message.
const message = db ?
Module.ccall('sqlite3_errmsg', 'string', ['number'], [db]) :
fname;
const error = new SQLiteError(message, rc);

if (db) {
databases.add(db);
try {
await sqlite3.close(db);
} catch {
// Closing an errored handle is best effort; preserve the open error.
} finally {
databases.delete(db);
}
}
throw error;
}

databases.add(db);
Module.ccall('RegisterExtensionFunctions', 'void', ['number'], [db]);
check(fname, rc);
return db;
} finally {
Module._sqlite3_free(zVfs);
Expand Down
43 changes: 43 additions & 0 deletions test/sqlite-api-open.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as SQLite from '../src/sqlite-api.js';

describe('SQLite API open cleanup', function() {
it('closes the database handle returned by a failed open', async function() {
const failedHandle = 42;
const close = jasmine.createSpy('sqlite3_close').and.resolveTo(SQLite.SQLITE_OK);
const registerExtensionFunctions = jasmine.createSpy('RegisterExtensionFunctions');
const module = {
HEAPU8: new Uint8Array(64),
_getSqliteFree: () => 0,
_malloc: () => 8,
_sqlite3_free: () => {},
_sqlite3_malloc: () => 32,
ccall: name => {
if (name === 'sqlite3_errmsg') return 'unable to open database file';
if (name === 'RegisterExtensionFunctions') registerExtensionFunctions();
},
cwrap: name => {
if (name === 'sqlite3_open_v2') {
return async () => SQLite.SQLITE_CANTOPEN;
}
if (name === 'sqlite3_close') return close;
return () => SQLite.SQLITE_OK;
},
getTempRet0: () => 0,
getValue: () => failedHandle,
vfs_register: () => SQLite.SQLITE_OK,
};
const sqlite3 = SQLite.Factory(module);

await expectAsync(sqlite3.open_v2('/cannot-open.db')).toBeRejectedWithError(
SQLite.SQLiteError,
'unable to open database file',
);

expect(close).toHaveBeenCalledOnceWith(failedHandle);
expect(registerExtensionFunctions).not.toHaveBeenCalled();
await expectAsync(sqlite3.close(failedHandle)).toBeRejectedWithError(
SQLite.SQLiteError,
'not a database',
);
});
});
Loading