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
20 changes: 20 additions & 0 deletions src/js/internal/sql/sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ class SQLiteAdapter implements DatabaseAdapter<BunSQLiteModule.Database, BunSQLi
public storedError: Error | null = null;
private _closed: boolean = false;
public queries: Set<Query<any, any>> = new Set();
/// One entry per begin() whose callback is still running; close() rejects them.
private closeHandlers: Set<(err: Error) => void> = new Set();

constructor(connectionInfo: Bun.SQL.__internal.DefinedSQLiteOptions) {
this.connectionInfo = connectionInfo;
Expand Down Expand Up @@ -474,6 +476,15 @@ class SQLiteAdapter implements DatabaseAdapter<BunSQLiteModule.Database, BunSQLi
const onclose = this.connectionInfo.onclose;
if (onclose) onclose(this.storedError);
} catch {}

const closeHandlers = this.closeHandlers;
if (closeHandlers.size > 0) {
this.closeHandlers = new Set();
const err = this.connectionClosedError();
for (const handler of closeHandlers) {
handler(err);
Comment thread
robobun marked this conversation as resolved.
}
}
}

flush() {
Expand All @@ -497,6 +508,15 @@ class SQLiteAdapter implements DatabaseAdapter<BunSQLiteModule.Database, BunSQLi
getConnectionForQuery(connection: BunSQLiteModule.Database): BunSQLiteModule.Database {
return connection;
}

attachConnectionCloseHandler(_connection: BunSQLiteModule.Database, handler: (err: Error) => void): void {
this.closeHandlers.add(handler);
}

detachConnectionCloseHandler(_connection: BunSQLiteModule.Database, handler: (err: Error) => void): void {
this.closeHandlers.delete(handler);
}

array(_values: any[], _typeNameOrID?: number | ArrayType): SQLArrayParameter {
throw new Error("SQLite doesn't support arrays");
}
Expand Down
44 changes: 44 additions & 0 deletions test/js/sql/sqlite-sql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2080,6 +2080,50 @@ describe("Connection management", () => {
}
});

test("close() rejects a begin() whose callback is still running", async () => {
const sql = new SQL("sqlite://:memory:");
const started = Promise.withResolvers<void>();
const begin = sql.begin(async tx => {
await tx`SELECT 1`;
started.resolve();
await new Promise(() => {});
});
const rejection = begin.catch(err => err);

await started.promise;
await sql.close();

// Like postgres and mysql after a forced close: begin() is settled by the time close() resolves.
expect(Bun.peek.status(begin)).toBe("rejected");
expect(await rejection).toMatchObject({
name: "SQLiteError",
code: "ERR_SQLITE_CONNECTION_CLOSED",
message: "Connection closed",
});
});

test("a begin() callback that outlives close() gets ERR_SQLITE_CONNECTION_CLOSED from its queries", async () => {
const sql = new SQL("sqlite://:memory:");
const started = Promise.withResolvers<void>();
const closed = Promise.withResolvers<void>();
const queryAfterClose = Promise.withResolvers<unknown>();
const begin = sql.begin(async tx => {
await tx`SELECT 1`;
started.resolve();
await closed.promise;
queryAfterClose.resolve(tx`SELECT 2`.catch(err => err));
});
const rejection = begin.catch(err => err);

await started.promise;
await sql.close();
closed.resolve();

const connectionClosed = { name: "SQLiteError", code: "ERR_SQLITE_CONNECTION_CLOSED" };
expect(await queryAfterClose.promise).toMatchObject(connectionClosed);
expect(await rejection).toMatchObject(connectionClosed);
});

test("reserve throws for SQLite", async () => {
const sql = new SQL("sqlite://:memory:");

Expand Down