Merge commit '36c66dd290d3ce6eb1ccd310d0c658d4a32bb8eb' as 'repos/effect'
This commit is contained in:
106
repos/effect/packages/sql/libsql/test/Client.test.ts
Normal file
106
repos/effect/packages/sql/libsql/test/Client.test.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { LibsqlClient } from "@effect/sql-libsql"
|
||||
import { assert, describe, layer } from "@effect/vitest"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { LibsqlContainer } from "./util.ts"
|
||||
|
||||
const Migrations = Layer.effectDiscard(
|
||||
LibsqlClient.LibsqlClient.pipe(
|
||||
Effect.andThen((sql) =>
|
||||
Effect.acquireRelease(
|
||||
sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)`,
|
||||
() => sql`DROP TABLE test;`.pipe(Effect.ignore)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
describe("Client", () => {
|
||||
layer(LibsqlContainer.layerClient, { timeout: "30 seconds" })((it) => {
|
||||
it.effect("should work", () =>
|
||||
Effect.gen(function*() {
|
||||
const sql = yield* LibsqlClient.LibsqlClient
|
||||
let response = yield* sql`INSERT INTO test (name) VALUES ('hello')`
|
||||
assert.deepStrictEqual(response, [])
|
||||
response = yield* sql`SELECT * FROM test`
|
||||
assert.deepStrictEqual(response, [{ id: 1, name: "hello" }])
|
||||
response = yield* sql`SELECT * FROM test`
|
||||
assert.deepStrictEqual(yield* sql`select * from test`.values, [
|
||||
[1, "hello"]
|
||||
])
|
||||
}).pipe(Effect.provide(Migrations)))
|
||||
|
||||
it.effect("should work with raw", () =>
|
||||
Effect.gen(function*() {
|
||||
const sql = yield* LibsqlClient.LibsqlClient
|
||||
let response: any
|
||||
response = yield* sql`CREATE TABLE test2 (id INTEGER PRIMARY KEY, name TEXT)`.raw
|
||||
yield* Effect.addFinalizer(() => sql`DROP TABLE test2;`.pipe(Effect.ignore))
|
||||
assert.deepStrictEqual(response.toJSON(), {
|
||||
columnTypes: [],
|
||||
columns: [],
|
||||
lastInsertRowid: null,
|
||||
rows: [],
|
||||
rowsAffected: 0
|
||||
})
|
||||
response = yield* sql`INSERT INTO test (name) VALUES ('hello')`.raw
|
||||
assert.deepStrictEqual(response.toJSON(), {
|
||||
columnTypes: [],
|
||||
columns: [],
|
||||
lastInsertRowid: "1",
|
||||
rows: [],
|
||||
rowsAffected: 1
|
||||
})
|
||||
response = yield* sql`SELECT * FROM test`.raw
|
||||
assert.deepStrictEqual(response.toJSON(), {
|
||||
columnTypes: ["INTEGER", "TEXT"],
|
||||
columns: ["id", "name"],
|
||||
lastInsertRowid: null,
|
||||
rows: [[1, "hello"]],
|
||||
rowsAffected: 0
|
||||
})
|
||||
}).pipe(Effect.provide(Migrations)))
|
||||
|
||||
it.effect("withTransaction", () =>
|
||||
Effect.gen(function*() {
|
||||
const sql = yield* LibsqlClient.LibsqlClient
|
||||
yield* sql.withTransaction(sql`INSERT INTO test (name) VALUES ('hello')`)
|
||||
const rows = yield* sql`SELECT * FROM test`
|
||||
assert.deepStrictEqual(rows, [{ id: 1, name: "hello" }])
|
||||
}).pipe(Effect.provide(Migrations)))
|
||||
|
||||
it.effect("withTransaction rollback", () =>
|
||||
Effect.gen(function*() {
|
||||
const sql = yield* LibsqlClient.LibsqlClient
|
||||
yield* sql`INSERT INTO test (name) VALUES ('hello')`.pipe(
|
||||
Effect.andThen(Effect.fail("boom")),
|
||||
sql.withTransaction,
|
||||
Effect.ignore
|
||||
)
|
||||
const rows = yield* sql`SELECT * FROM test`
|
||||
assert.deepStrictEqual(rows, [])
|
||||
}).pipe(Effect.provide(Migrations)))
|
||||
|
||||
it.effect("withTransaction nested", () =>
|
||||
Effect.gen(function*() {
|
||||
const sql = yield* LibsqlClient.LibsqlClient
|
||||
const stmt = sql`INSERT INTO test (name) VALUES ('hello')`
|
||||
|
||||
yield* stmt.pipe(Effect.andThen(() => stmt.pipe(sql.withTransaction)), sql.withTransaction)
|
||||
const rows = yield* sql<{ total_rows: number }>`select count(*) as total_rows FROM test`
|
||||
assert.deepStrictEqual(rows.at(0)?.total_rows, 2)
|
||||
}).pipe(Effect.provide(Migrations)))
|
||||
|
||||
it.effect("withTransaction nested rollback", () =>
|
||||
Effect.gen(function*() {
|
||||
const sql = yield* LibsqlClient.LibsqlClient
|
||||
const stmt = sql`INSERT INTO test (name) VALUES ('hello')`
|
||||
|
||||
yield* stmt.pipe(
|
||||
Effect.andThen(() => stmt.pipe(Effect.andThen(Effect.fail("boom")), sql.withTransaction, Effect.ignore)),
|
||||
sql.withTransaction
|
||||
)
|
||||
const rows = yield* sql<{ total_rows: number }>`select count(*) as total_rows FROM test`
|
||||
assert.deepStrictEqual(rows.at(0)?.total_rows, 1)
|
||||
}).pipe(Effect.provide(Migrations)))
|
||||
})
|
||||
})
|
||||
177
repos/effect/packages/sql/libsql/test/Resolver.test.ts
Normal file
177
repos/effect/packages/sql/libsql/test/Resolver.test.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { LibsqlClient } from "@effect/sql-libsql"
|
||||
import { assert, describe, layer } from "@effect/vitest"
|
||||
import { Cause, Effect, Iterable } from "effect"
|
||||
import * as Schema from "effect/Schema"
|
||||
import { SqlError, SqlResolver } from "effect/unstable/sql"
|
||||
import { LibsqlContainer } from "./util.ts"
|
||||
|
||||
const seededClient = Effect.gen(function*() {
|
||||
const sql = yield* LibsqlClient.LibsqlClient
|
||||
yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)`
|
||||
for (const id of Iterable.range(1, 100)) {
|
||||
yield* sql`INSERT INTO test ${sql.insert({ id, name: `name${id}` })}`
|
||||
}
|
||||
yield* Effect.addFinalizer(() => sql`DROP TABLE test;`.pipe(Effect.orDie))
|
||||
return sql
|
||||
})
|
||||
|
||||
layer(LibsqlContainer.layerClient, { timeout: "30 seconds" })("Resolver", (it) => {
|
||||
describe.sequential("ordered", () => {
|
||||
it.effect("insert", () =>
|
||||
Effect.gen(function*() {
|
||||
const batches: Array<Array<string>> = []
|
||||
const sql = yield* seededClient
|
||||
const Insert = SqlResolver.ordered({
|
||||
Request: Schema.String,
|
||||
Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
|
||||
execute: (names) => {
|
||||
batches.push(names)
|
||||
return sql`INSERT INTO test ${sql.insert(names.map((name) => ({ name })))} RETURNING *`
|
||||
}
|
||||
})
|
||||
const execute = SqlResolver.request(Insert)
|
||||
assert.deepStrictEqual(
|
||||
yield* Effect.all({
|
||||
one: execute("one"),
|
||||
two: execute("two")
|
||||
}, { concurrency: "unbounded" }),
|
||||
{
|
||||
one: { id: 101, name: "one" },
|
||||
two: { id: 102, name: "two" }
|
||||
}
|
||||
)
|
||||
assert.deepStrictEqual(batches, [["one", "two"]])
|
||||
}))
|
||||
|
||||
it.effect("result length mismatch", () =>
|
||||
Effect.gen(function*() {
|
||||
const batches: Array<Array<number>> = []
|
||||
const sql = yield* seededClient
|
||||
const Select = SqlResolver.ordered({
|
||||
Request: Schema.Number,
|
||||
Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
|
||||
execute: (ids) => {
|
||||
batches.push(ids)
|
||||
return sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}`
|
||||
}
|
||||
})
|
||||
const execute = SqlResolver.request(Select)
|
||||
const error = yield* Effect.all([
|
||||
execute(1),
|
||||
execute(2),
|
||||
execute(3),
|
||||
execute(101)
|
||||
], { concurrency: "unbounded" }).pipe(
|
||||
Effect.flip
|
||||
)
|
||||
assert(error instanceof SqlError.ResultLengthMismatch)
|
||||
assert.strictEqual(error.actual, 3)
|
||||
assert.strictEqual(error.expected, 4)
|
||||
assert.deepStrictEqual(batches, [[1, 2, 3, 101]])
|
||||
}))
|
||||
})
|
||||
|
||||
describe.sequential("grouped", () => {
|
||||
it.effect("find by name", () =>
|
||||
Effect.gen(function*() {
|
||||
const sql = yield* seededClient
|
||||
const FindByName = SqlResolver.grouped({
|
||||
Request: Schema.String,
|
||||
RequestGroupKey: (name) => name,
|
||||
Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
|
||||
ResultGroupKey: (result) => result.name,
|
||||
execute: (names) => sql`SELECT * FROM test WHERE name IN ${sql.in(names)}`
|
||||
})
|
||||
yield* sql`INSERT INTO test ${sql.insert({ name: "name1" })}`
|
||||
const execute = SqlResolver.request(FindByName)
|
||||
assert.deepStrictEqual(
|
||||
yield* Effect.all({
|
||||
one: execute("name1"),
|
||||
two: execute("name2"),
|
||||
three: Effect.flip(execute("name0"))
|
||||
}, { concurrency: "unbounded" }),
|
||||
{
|
||||
one: [{ id: 1, name: "name1" }, { id: 101, name: "name1" }],
|
||||
two: [{ id: 2, name: "name2" }],
|
||||
three: new Cause.NoSuchElementError()
|
||||
}
|
||||
)
|
||||
}))
|
||||
|
||||
it.effect("using raw rows", () =>
|
||||
Effect.gen(function*() {
|
||||
const sql = yield* seededClient
|
||||
const FindByName = SqlResolver.grouped({
|
||||
Request: Schema.String,
|
||||
RequestGroupKey: (name) => name,
|
||||
Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
|
||||
ResultGroupKey: (_, result: any) => result.name,
|
||||
execute: (names) => sql`SELECT * FROM test WHERE name IN ${sql.in(names)}`
|
||||
})
|
||||
yield* sql`INSERT INTO test ${sql.insert({ name: "name1" })}`
|
||||
const execute = SqlResolver.request(FindByName)
|
||||
assert.deepStrictEqual(
|
||||
yield* Effect.all({
|
||||
one: execute("name1"),
|
||||
two: execute("name2"),
|
||||
three: Effect.flip(execute("name0"))
|
||||
}, { concurrency: "unbounded" }),
|
||||
{
|
||||
one: [{ id: 1, name: "name1" }, { id: 101, name: "name1" }],
|
||||
two: [{ id: 2, name: "name2" }],
|
||||
three: new Cause.NoSuchElementError()
|
||||
}
|
||||
)
|
||||
}))
|
||||
})
|
||||
|
||||
describe.sequential("findById", () => {
|
||||
it.effect("find by id", () =>
|
||||
Effect.gen(function*() {
|
||||
const sql = yield* seededClient
|
||||
const FindById = SqlResolver.findById({
|
||||
Id: Schema.Number,
|
||||
Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
|
||||
ResultId: (result) => result.id,
|
||||
execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}`
|
||||
})
|
||||
const execute = SqlResolver.request(FindById)
|
||||
assert.deepStrictEqual(
|
||||
yield* Effect.all({
|
||||
one: execute(1),
|
||||
two: execute(2),
|
||||
three: Effect.flip(execute(101))
|
||||
}, { concurrency: "unbounded" }),
|
||||
{
|
||||
one: { id: 1, name: "name1" },
|
||||
two: { id: 2, name: "name2" },
|
||||
three: new Cause.NoSuchElementError()
|
||||
}
|
||||
)
|
||||
}))
|
||||
|
||||
it.effect("using raw rows", () =>
|
||||
Effect.gen(function*() {
|
||||
const sql = yield* seededClient
|
||||
const FindById = SqlResolver.findById({
|
||||
Id: Schema.Number,
|
||||
Result: Schema.Struct({ id: Schema.Number, name: Schema.String }),
|
||||
ResultId: (_, result: any) => result.id,
|
||||
execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}`
|
||||
})
|
||||
const execute = SqlResolver.request(FindById)
|
||||
assert.deepStrictEqual(
|
||||
yield* Effect.all({
|
||||
one: execute(1),
|
||||
two: execute(2),
|
||||
three: Effect.flip(execute(101))
|
||||
}, { concurrency: "unbounded" }),
|
||||
{
|
||||
one: { id: 1, name: "name1" },
|
||||
two: { id: 2, name: "name2" },
|
||||
three: new Cause.NoSuchElementError()
|
||||
}
|
||||
)
|
||||
}))
|
||||
})
|
||||
})
|
||||
54
repos/effect/packages/sql/libsql/test/util.ts
Normal file
54
repos/effect/packages/sql/libsql/test/util.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { LibsqlClient } from "@effect/sql-libsql"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { GenericContainer, type StartedTestContainer } from "testcontainers"
|
||||
|
||||
const waitForServer = async (container: StartedTestContainer) => {
|
||||
const url = `http://${container.getHost()}:${container.getMappedPort(8080)}`
|
||||
let error: unknown = undefined
|
||||
|
||||
for (let i = 0; i < 60; i++) {
|
||||
try {
|
||||
await fetch(url)
|
||||
return
|
||||
} catch (cause) {
|
||||
error = cause
|
||||
await new Promise((resolve) => setTimeout(resolve, 250))
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
export class LibsqlContainer extends Context.Service<
|
||||
LibsqlContainer,
|
||||
StartedTestContainer
|
||||
>()("test/LibsqlContainer") {
|
||||
static readonly layer = Layer.effect(this)(
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(async () => {
|
||||
const container = await new GenericContainer("ghcr.io/tursodatabase/libsql-server:main")
|
||||
.withExposedPorts(8080)
|
||||
.withEnvironment({ SQLD_NODE: "primary" })
|
||||
.withCommand(["sqld", "--no-welcome", "--http-listen-addr", "0.0.0.0:8080"]).start()
|
||||
|
||||
try {
|
||||
await waitForServer(container)
|
||||
return container
|
||||
} catch (error) {
|
||||
await container.stop()
|
||||
throw error
|
||||
}
|
||||
}),
|
||||
(container) => Effect.promise(() => container.stop())
|
||||
)
|
||||
)
|
||||
|
||||
static layerClient = Layer.unwrap(
|
||||
Effect.gen(function*() {
|
||||
const container = yield* LibsqlContainer
|
||||
return LibsqlClient.layer({
|
||||
url: `http://${container.getHost()}:${container.getMappedPort(8080)}`
|
||||
})
|
||||
})
|
||||
).pipe(Layer.provide(this.layer))
|
||||
}
|
||||
Reference in New Issue
Block a user