1import { NodeFileSystem } from "@effect/platform-node"
2import { SqliteClient } from "@effect/sql-sqlite-node"
3import { assert, describe, it } from "@effect/vitest"
4import { Cause, Effect, FileSystem, Iterable, Schema } from "effect"
5import { Reactivity } from "effect/reactivity"
6import { SqlError, SqlResolver } from "effect/sql"
7
8const makeClient = Effect.gen(function*() {
9 const fs = yield* FileSystem.FileSystem
10 const dir = yield* fs.makeTempDirectoryScoped()
11 return yield* SqliteClient.make({
12 filename: dir + "/test.db"
13 })
14}).pipe(Effect.provide([NodeFileSystem.layer, Reactivity.layer]))
15
16const seededClient = Effect.gen(function*() {
17 const sql = yield* makeClient
18 yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)`
19 for (const id of Iterable.range(1, 100)) {
20 yield* sql`INSERT INTO test ${sql.insert({ id, name: `name${id}` })}`
21 }
22 return sql
23})
24
25describe("Resolver", () => {
26 describe("ordered", () => {
27 it.effect("insert", () =>
28 Effect.gen(function*() {
29 const batches: Array<Array<string>> = []
30 const sql = yield* seededClient
31 const Insert = SqlResolver.ordered({
32 Request: Schema.String,
33 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
34 execute: (names) => {
35 batches.push(names)
36 return sql`INSERT INTO test ${sql.insert(names.map((name) => ({ name })))} RETURNING *`
37 }
38 })
39 const execute = SqlResolver.request(Insert)
40 assert.deepStrictEqual(
41 yield* Effect.all({
42 one: execute("one"),
43 two: execute("two")
44 }, { concurrency: "unbounded" }),
45 {
46 one: { id: 101, name: "one" },
47 two: { id: 102, name: "two" }
48 }
49 )
50 assert.deepStrictEqual(batches, [["one", "two"]])
51 }))
52
53 it.effect("result length mismatch", () =>
54 Effect.gen(function*() {
55 const batches: Array<Array<number>> = []
56 const sql = yield* seededClient
57 const Select = SqlResolver.ordered({
58 Request: Schema.Number,
59 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
60 execute: (ids) => {
61 batches.push(ids)
62 return sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}`
63 }
64 })
65 const execute = SqlResolver.request(Select)
66 const error = yield* Effect.all([
67 execute(1),
68 execute(2),
69 execute(3),
70 execute(101)
71 ], { concurrency: "unbounded" })
72 .pipe(Effect.flip)
73 assert(error instanceof SqlError.ResultLengthMismatch)
74 assert.strictEqual(error.actual, 3)
75 assert.strictEqual(error.expected, 4)
76 assert.deepStrictEqual(batches, [[1, 2, 3, 101]])
77 }))
78 })
79
80 describe("grouped", () => {
81 it.effect("find by name", () =>
82 Effect.gen(function*() {
83 const sql = yield* seededClient
84 const FindByName = SqlResolver.grouped({
85 Request: Schema.String,
86 RequestGroupKey: (name) => name,
87 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
88 ResultGroupKey: (result) => result.name,
89 execute: (names) => sql`SELECT * FROM test WHERE name IN ${sql.in(names)}`
90 })
91 yield* sql`INSERT INTO test ${sql.insert({ name: "name1" })}`
92 const execute = SqlResolver.request(FindByName)
93 assert.deepStrictEqual(
94 yield* Effect.all({
95 one: execute("name1"),
96 two: execute("name2"),
97 three: Effect.flip(execute("name0"))
98 }, { concurrency: "unbounded" }),
99 {
100 one: [{ id: 1, name: "name1" }, { id: 101, name: "name1" }],
101 two: [{ id: 2, name: "name2" }],
102 three: new Cause.NoSuchElementError()
103 }
104 )
105 }))
106
107 it.effect("using raw rows", () =>
108 Effect.gen(function*() {
109 const sql = yield* seededClient
110 const FindByName = SqlResolver.grouped({
111 Request: Schema.String,
112 RequestGroupKey: (name) => name,
113 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
114 ResultGroupKey: (_, result: any) => result.name,
115 execute: (names) => sql`SELECT * FROM test WHERE name IN ${sql.in(names)}`
116 })
117 yield* sql`INSERT INTO test ${sql.insert({ name: "name1" })}`
118 const execute = SqlResolver.request(FindByName)
119 assert.deepStrictEqual(
120 yield* Effect.all({
121 one: execute("name1"),
122 two: execute("name2"),
123 three: Effect.flip(execute("name0"))
124 }, { concurrency: "unbounded" }),
125 {
126 one: [{ id: 1, name: "name1" }, { id: 101, name: "name1" }],
127 two: [{ id: 2, name: "name2" }],
128 three: new Cause.NoSuchElementError()
129 }
130 )
131 }))
132 })
133
134 describe("findById", () => {
135 it.effect("find by id", () =>
136 Effect.gen(function*() {
137 const sql = yield* seededClient
138 const FindById = SqlResolver.findById({
139 Id: Schema.Number,
140 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
141 ResultId: (result) => result.id,
142 execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}`
143 })
144 const execute = SqlResolver.request(FindById)
145 assert.deepStrictEqual(
146 yield* Effect.all({
147 one: execute(1),
148 two: execute(2),
149 three: Effect.flip(execute(101))
150 }, { concurrency: "unbounded" }),
151 {
152 one: { id: 1, name: "name1" },
153 two: { id: 2, name: "name2" },
154 three: new Cause.NoSuchElementError()
155 }
156 )
157 }))
158
159 it.effect("using raw rows", () =>
160 Effect.gen(function*() {
161 const sql = yield* seededClient
162 const FindById = SqlResolver.findById({
163 Id: Schema.Number,
164 Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
165 ResultId: (_, result: any) => result.id,
166 execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}`
167 })
168 const execute = SqlResolver.request(FindById)
169 assert.deepStrictEqual(
170 yield* Effect.all({
171 one: execute(1),
172 two: execute(2),
173 three: Effect.flip(execute(101))
174 }, { concurrency: "unbounded" }),
175 {
176 one: { id: 1, name: "name1" },
177 two: { id: 2, name: "name2" },
178 three: new Cause.NoSuchElementError()
179 }
180 )
181 }))
182 })
183})