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