| ... | @@ -1,6 +1,10 @@ | ... | @@ -1,6 +1,10 @@ |
| | 1 | import * as NodeSqlite from "node:sqlite"; |
| | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 1 | import { assert, it } from "@effect/vitest"; | 3 | import { assert, it } from "@effect/vitest"; |
| 2 | import * as Effect from "effect/Effect"; | 4 | import * as Effect from "effect/Effect"; |
| | 5 | import * as FileSystem from "effect/FileSystem"; |
| 3 | import * as Layer from "effect/Layer"; | 6 | import * as Layer from "effect/Layer"; |
| | 7 | import * as Path from "effect/Path"; |
| 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 8 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 5 | | 9 | |
| 6 | import * as SqliteClient from "./nodeSqliteClient.ts"; | 10 | import * as SqliteClient from "./nodeSqliteClient.ts"; |
| ... | @@ -8,6 +12,21 @@ import * as SqliteClient from "./nodeSqliteClient.ts"; | ... | @@ -8,6 +12,21 @@ import * as SqliteClient from "./nodeSqliteClient.ts"; |
| 8 | const layer = it.layer(SqliteClient.layer({ filename: ":memory:" })); | 12 | const layer = it.layer(SqliteClient.layer({ filename: ":memory:" })); |
| 9 | | 13 | |
| 10 | layer("NodeSqliteClient", (it) => { | 14 | layer("NodeSqliteClient", (it) => { |
| | 15 | it.effect("retries preparing a query after the missing schema becomes available", () => |
| | 16 | Effect.gen(function* () { |
| | 17 | const sql = yield* SqlClient.SqlClient; |
| | 18 | const select = sql<{ name: string }>`SELECT name FROM created_after_prepare_failure`; |
| | 19 | const error = yield* select.pipe(Effect.flip); |
| | 20 | assert.equal(error._tag, "SqlError"); |
| | 21 | assert.equal(error.reason.operation, "prepare"); |
| | 22 | |
| | 23 | yield* sql`CREATE TABLE created_after_prepare_failure(name TEXT NOT NULL)`; |
| | 24 | yield* sql`INSERT INTO created_after_prepare_failure VALUES ('recovered')`; |
| | 25 | assert.deepEqual(yield* select, [{ name: "recovered" }]); |
| | 26 | assert.deepEqual(yield* select.values, [["recovered"]]); |
| | 27 | }), |
| | 28 | ); |
| | 29 | |
| 11 | it.effect("runs prepared queries and returns positional values", () => | 30 | it.effect("runs prepared queries and returns positional values", () => |
| 12 | Effect.gen(function* () { | 31 | Effect.gen(function* () { |
| 13 | const sql = yield* SqlClient.SqlClient; | 32 | const sql = yield* SqlClient.SqlClient; |
| ... | @@ -54,3 +73,32 @@ it.effect("returns a typed failure when the database cannot be opened", () => | ... | @@ -54,3 +73,32 @@ it.effect("returns a typed failure when the database cannot be opened", () => |
| 54 | assert.equal(error.reason.operation, "open"); | 73 | assert.equal(error.reason.operation, "open"); |
| 55 | }), | 74 | }), |
| 56 | ); | 75 | ); |
| | 76 | |
| | 77 | it.effect( |
| | 78 | "recovers a prepared query immediately after an exclusive database lock is released", |
| | 79 | () => |
| | 80 | Effect.gen(function* () { |
| | 81 | const fs = yield* FileSystem.FileSystem; |
| | 82 | const path = yield* Path.Path; |
| | 83 | const directory = yield* fs.makeTempDirectoryScoped({ prefix: "t3-sqlite-prepare-" }); |
| | 84 | const filename = path.join(directory, "state.sqlite"); |
| | 85 | const blocker = yield* Effect.acquireRelease( |
| | 86 | Effect.sync(() => new NodeSqlite.DatabaseSync(filename)), |
| | 87 | (database) => Effect.sync(() => database.close()), |
| | 88 | ); |
| | 89 | yield* Effect.sync(() => { |
| | 90 | blocker.exec("CREATE TABLE entries(value TEXT); INSERT INTO entries VALUES ('retained')"); |
| | 91 | }); |
| | 92 | yield* Effect.gen(function* () { |
| | 93 | const sql = yield* SqlClient.SqlClient; |
| | 94 | yield* Effect.sync(() => blocker.exec("BEGIN EXCLUSIVE")); |
| | 95 | const select = sql`SELECT value FROM entries`; |
| | 96 | const error = yield* select.values.pipe(Effect.flip); |
| | 97 | assert.equal(error._tag, "SqlError"); |
| | 98 | assert.equal(error.reason.operation, "prepare"); |
| | 99 | yield* Effect.sync(() => blocker.exec("ROLLBACK")); |
| | 100 | assert.deepEqual(yield* select.values, [["retained"]]); |
| | 101 | assert.deepEqual(yield* select, [{ value: "retained" }]); |
| | 102 | }).pipe(Effect.provide(SqliteClient.layer({ filename }))); |
| | 103 | }).pipe(Effect.provide(NodeServices.layer)), |
| | 104 | ); |