| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import { Cause, Effect, Exit } from "effect" |
| 3 | import { Reactivity } from "effect/reactivity" |
| 4 | import { ConnectionError, SqlError } from "effect/sql/SqlError" |
| 5 | |
| 6 | // The wa-sqlite loader fetches its wasm from a file URL, which Node's fetch |
| 7 | // does not support. |
| 8 | const isBun = "bun" in process.versions |
| 9 | |
| 10 | describe("Memory", () => { |
| 11 | it.effect.skipIf(!isBun)("export does not bypass failed-commit cleanup", () => |
| 12 | Effect.gen(function*() { |
| 13 | const { SqliteClient } = yield* Effect.promise(() => import("@effect/sql-sqlite-wasm")) |
| 14 | const sql = yield* SqliteClient.makeMemory({}) |
| 15 | yield* sql`PRAGMA foreign_keys = ON` |
| 16 | yield* sql`CREATE TABLE parent (id INTEGER PRIMARY KEY)` |
| 17 | yield* sql`CREATE TABLE child (parent_id INTEGER REFERENCES parent(id) DEFERRABLE INITIALLY DEFERRED)` |
| 18 | |
| 19 | // Fail only the cleanup ROLLBACK, leaving the deferred constraint failure real. |
| 20 | const conn = yield* Effect.scoped(sql.reserve) |
| 21 | const executeUnprepared = conn.executeUnprepared |
| 22 | let failRollback = true |
| 23 | Object.defineProperty(conn, "executeUnprepared", { |
| 24 | configurable: true, |
| 25 | value: (...args: Parameters<typeof executeUnprepared>) => |
| 26 | args[0] === "ROLLBACK" && failRollback |
| 27 | ? Effect.fail( |
| 28 | new SqlError({ |
| 29 | reason: new ConnectionError({ |
| 30 | message: "injected rollback failure", |
| 31 | operation: "rollback", |
| 32 | cause: new Error("injected rollback failure") |
| 33 | }) |
| 34 | }) |
| 35 | ) |
| 36 | : executeUnprepared.apply(conn, args) |
| 37 | }) |
| 38 | const failedCommit = yield* Effect.exit(sql.withTransaction(sql`INSERT INTO child VALUES (999)`)) |
| 39 | assert.isTrue(Exit.isFailure(failedCommit)) |
| 40 | |
| 41 | const rejected = yield* Effect.exit(sql.export) |
| 42 | assert.isTrue(Exit.isFailure(rejected), "export bypassed the rejected connection") |
| 43 | if (Exit.isFailure(rejected)) { |
| 44 | assert.match(Cause.pretty(rejected.cause), /cannot be reused after failed COMMIT cleanup/i) |
| 45 | } |
| 46 | |
| 47 | failRollback = false |
| 48 | const snapshot = yield* SqliteClient.makeMemory({}) |
| 49 | yield* snapshot.import(yield* sql.export) |
| 50 | assert.deepStrictEqual(yield* snapshot`SELECT * FROM child`, []) |
| 51 | }).pipe(Effect.provide(Reactivity.layer))) |
| 52 | }) |