diff --git a/src/infra/kysely-node-sqlite.test.ts b/src/infra/kysely-node-sqlite.test.ts index 49081bf5b5c..171ed317e25 100644 --- a/src/infra/kysely-node-sqlite.test.ts +++ b/src/infra/kysely-node-sqlite.test.ts @@ -1,6 +1,6 @@ import { DatabaseSync } from "node:sqlite"; -import { Kysely, sql, type Generated } from "kysely"; -import { afterEach, describe, expect, it } from "vitest"; +import { CompiledQuery, Kysely, sql, type Generated } from "kysely"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { NodeSqliteKyselyDialect } from "./kysely-node-sqlite.js"; type TestDatabase = { @@ -19,19 +19,7 @@ describe("NodeSqliteKyselyDialect", () => { }); it("uses node:sqlite with raw row-returning queries and returning clauses", async () => { - db = new Kysely({ - dialect: new NodeSqliteKyselyDialect({ - database: new DatabaseSync(":memory:"), - }), - }); - - await db.schema - .createTable("person") - .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement()) - .addColumn("name", "text", (col) => col.notNull()) - .execute(); - - await db.insertInto("person").values({ name: "Ada" }).execute(); + db = await createTestDb(); await expect(db.selectFrom("person").selectAll().execute()).resolves.toEqual([ { id: 1, name: "Ada" }, @@ -60,4 +48,124 @@ describe("NodeSqliteKyselyDialect", () => { expect(update.insertId).toBeUndefined(); expect(update.numAffectedRows).toBe(1n); }); + + it("creates the database lazily and runs the connection hook once", async () => { + const sqlite = new DatabaseSync(":memory:"); + const createDatabase = vi.fn(() => sqlite); + const onCreateConnection = vi.fn(async (connection) => { + await connection.executeQuery(CompiledQuery.raw("pragma user_version = 7")); + }); + + db = new Kysely({ + dialect: new NodeSqliteKyselyDialect({ + database: createDatabase, + onCreateConnection, + }), + }); + + await expect( + sql<{ user_version: number }>`pragma user_version`.execute(db), + ).resolves.toMatchObject({ + rows: [{ user_version: 7 }], + }); + expect(createDatabase).toHaveBeenCalledTimes(1); + expect(onCreateConnection).toHaveBeenCalledTimes(1); + }); + + it("returns insert metadata only for changed insert statements", async () => { + db = new Kysely({ + dialect: new NodeSqliteKyselyDialect({ + database: new DatabaseSync(":memory:"), + }), + }); + await createPersonTable(db); + + const insertResult = await db + .insertInto("person") + .values({ name: "Ada" }) + .executeTakeFirstOrThrow(); + expect(insertResult.insertId).toBe(1n); + expect(insertResult.numInsertedOrUpdatedRows).toBe(1n); + + const updateResult = await db + .updateTable("person") + .set({ name: "Ada Lovelace" }) + .where("id", "=", 1) + .executeTakeFirstOrThrow(); + expect(updateResult.numUpdatedRows).toBe(1n); + + const ignoredInsert = await sql` + insert or ignore into person (id, name) values (${1}, ${"Ada Again"}) + `.execute(db); + expect(ignoredInsert.insertId).toBeUndefined(); + expect(ignoredInsert.numAffectedRows).toBe(0n); + }); + + it("rolls back transactions and controlled savepoints", async () => { + db = new Kysely({ + dialect: new NodeSqliteKyselyDialect({ + database: new DatabaseSync(":memory:"), + }), + }); + await createPersonTable(db); + + await expect( + db.transaction().execute(async (trx) => { + await trx.insertInto("person").values({ name: "Rollback" }).execute(); + throw new Error("rollback outer"); + }), + ).rejects.toThrow("rollback outer"); + await expect(db.selectFrom("person").selectAll().execute()).resolves.toEqual([]); + + const trx = await db.startTransaction().execute(); + await trx.insertInto("person").values({ name: "Ada" }).execute(); + const afterAda = await trx.savepoint("after_ada").execute(); + await afterAda.insertInto("person").values({ name: "Grace" }).execute(); + const afterRollback = await afterAda.rollbackToSavepoint("after_ada").execute(); + await afterRollback.insertInto("person").values({ name: "Lin" }).execute(); + await afterRollback.commit().execute(); + + await expect(db.selectFrom("person").select("name").orderBy("id").execute()).resolves.toEqual([ + { name: "Ada" }, + { name: "Lin" }, + ]); + }); + + it("streams selected rows through node:sqlite iteration", async () => { + db = await createTestDb(); + await db + .insertInto("person") + .values([{ name: "Grace" }, { name: "Lin" }]) + .execute(); + + const rows: Array<{ id: number; name: string }> = []; + for await (const row of db.selectFrom("person").selectAll().orderBy("id").stream(1)) { + rows.push(row); + } + + expect(rows).toEqual([ + { id: 1, name: "Ada" }, + { id: 2, name: "Grace" }, + { id: 3, name: "Lin" }, + ]); + }); }); + +async function createTestDb(): Promise> { + const testDb = new Kysely({ + dialect: new NodeSqliteKyselyDialect({ + database: new DatabaseSync(":memory:"), + }), + }); + await createPersonTable(testDb); + await testDb.insertInto("person").values({ name: "Ada" }).execute(); + return testDb; +} + +async function createPersonTable(testDb: Kysely): Promise { + await testDb.schema + .createTable("person") + .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement()) + .addColumn("name", "text", (col) => col.notNull()) + .execute(); +}