From 781ca741ddc6865fae7d1147062ca8e98c86fa51 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 6 Aug 2023 22:21:46 -0500 Subject: [PATCH] Implement the DenoSqliteDriver correctly --- lib/kysely-deno-sqlite/deps.ts | 6 +- .../src/deno-sqlite-driver.ts | 123 +++++++++++++----- 2 files changed, 93 insertions(+), 36 deletions(-) diff --git a/lib/kysely-deno-sqlite/deps.ts b/lib/kysely-deno-sqlite/deps.ts index ff95c11..71a9fa8 100644 --- a/lib/kysely-deno-sqlite/deps.ts +++ b/lib/kysely-deno-sqlite/deps.ts @@ -1,17 +1,17 @@ export { + CompiledQuery, + type DatabaseConnection, type DatabaseIntrospector, type Dialect, type DialectAdapter, type Driver, Kysely, type QueryCompiler, + type QueryResult, SqliteAdapter, - type SqliteDatabase, type SqliteDialectConfig, - SqliteDriver, SqliteIntrospector, SqliteQueryCompiler, - type SqliteStatement, } from 'npm:kysely@^0.25.0'; export type { DB as DenoSqlite } from 'https://deno.land/x/sqlite@v3.7.3/mod.ts'; diff --git a/lib/kysely-deno-sqlite/src/deno-sqlite-driver.ts b/lib/kysely-deno-sqlite/src/deno-sqlite-driver.ts index c68809e..b0d4655 100644 --- a/lib/kysely-deno-sqlite/src/deno-sqlite-driver.ts +++ b/lib/kysely-deno-sqlite/src/deno-sqlite-driver.ts @@ -1,50 +1,107 @@ -import { type DenoSqlite, type SqliteDatabase, SqliteDriver, type SqliteStatement } from '../deps.ts'; +import { CompiledQuery, type DatabaseConnection, type DenoSqlite, type Driver, type QueryResult } from '../deps.ts'; import type { DenoSqliteDialectConfig } from './deno-sqlite-dialect-config.ts'; -class DenoSqliteDriver extends SqliteDriver { +class DenoSqliteDriver implements Driver { + readonly #config: DenoSqliteDialectConfig; + readonly #connectionMutex = new ConnectionMutex(); + + #db?: DenoSqlite; + #connection?: DatabaseConnection; + constructor(config: DenoSqliteDialectConfig) { - super({ - ...config, - database: async () => - new DenoSqliteDatabase( - typeof config.database === 'function' ? await config.database() : config.database, - ), - }); + this.#config = Object.freeze({ ...config }); + } + + async init(): Promise { + this.#db = typeof this.#config.database === 'function' ? await this.#config.database() : this.#config.database; + + this.#connection = new DenoSqliteConnection(this.#db); + + if (this.#config.onCreateConnection) { + await this.#config.onCreateConnection(this.#connection); + } + } + + async acquireConnection(): Promise { + // SQLite only has one single connection. We use a mutex here to wait + // until the single connection has been released. + await this.#connectionMutex.lock(); + return this.#connection!; + } + + async beginTransaction(connection: DatabaseConnection): Promise { + await connection.executeQuery(CompiledQuery.raw('begin')); + } + + async commitTransaction(connection: DatabaseConnection): Promise { + await connection.executeQuery(CompiledQuery.raw('commit')); + } + + async rollbackTransaction(connection: DatabaseConnection): Promise { + await connection.executeQuery(CompiledQuery.raw('rollback')); + } + + // deno-lint-ignore require-await + async releaseConnection(): Promise { + this.#connectionMutex.unlock(); + } + + // deno-lint-ignore require-await + async destroy(): Promise { + this.#db?.close(); } } -/** HACK: This is an adapter class. */ -class DenoSqliteDatabase implements SqliteDatabase { - #db: DenoSqlite; +class DenoSqliteConnection implements DatabaseConnection { + readonly #db: DenoSqlite; constructor(db: DenoSqlite) { this.#db = db; } - close(): void { - this.#db.close(); + executeQuery({ sql, parameters }: CompiledQuery): Promise> { + const query = this.#db.prepareQuery(sql); + + const rows = query.allEntries(parameters as any); + const { changes, lastInsertRowId } = this.#db; + + query.finalize(); + + return Promise.resolve({ + rows: rows as O[], + numAffectedRows: BigInt(changes), + insertId: BigInt(lastInsertRowId), + }); } - prepare(sql: string): SqliteStatement { - const query = this.#db.prepareQuery(sql); - return { - // HACK: implement an actual driver to fix this. - reader: true, - all: (parameters: ReadonlyArray) => { - const result = query.allEntries(parameters as any); - query.finalize(); - return result; - }, - run: (parameters: ReadonlyArray) => { - query.execute(parameters as any); - query.finalize(); - return { - changes: this.#db.changes, - lastInsertRowid: this.#db.lastInsertRowId, - }; - }, - }; + // deno-lint-ignore require-yield + async *streamQuery(): AsyncIterableIterator> { + throw new Error('Sqlite driver doesn\'t support streaming'); + } +} + +class ConnectionMutex { + #promise?: Promise; + #resolve?: () => void; + + async lock(): Promise { + while (this.#promise) { + await this.#promise; + } + + this.#promise = new Promise((resolve) => { + this.#resolve = resolve; + }); + } + + unlock(): void { + const resolve = this.#resolve; + + this.#promise = undefined; + this.#resolve = undefined; + + resolve?.(); } }