Merge commit '36c66dd290d3ce6eb1ccd310d0c658d4a32bb8eb' as 'repos/effect'
This commit is contained in:
34
repos/effect/packages/sql/mysql2/test/Client.test.ts
Normal file
34
repos/effect/packages/sql/mysql2/test/Client.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { Effect, Layer, Logger } from "effect"
|
||||
import { SqlClient, Statement } from "effect/unstable/sql"
|
||||
import { MysqlContainer } from "./utils.ts"
|
||||
|
||||
const SqlLogger = Layer.succeed(Statement.CurrentTransformer)((statement) => {
|
||||
const [query, params] = statement.compile()
|
||||
return Effect.log("executing sql").pipe(
|
||||
Effect.annotateLogs({ query, params }),
|
||||
Effect.as(statement)
|
||||
)
|
||||
})
|
||||
|
||||
describe("sql", () => {
|
||||
it.effect("tranformers", () =>
|
||||
Effect.gen(function*() {
|
||||
const logs: Array<unknown> = []
|
||||
const sql = yield* SqlClient.SqlClient
|
||||
const result = yield* sql<{ result: number }>`SELECT 1 + 1 AS result`.pipe(
|
||||
Effect.provide(Logger.layer([
|
||||
Logger.make((log) => {
|
||||
logs.push(log.message)
|
||||
})
|
||||
]))
|
||||
)
|
||||
assert.deepStrictEqual(result, [{ result: 2 }])
|
||||
assert.deepStrictEqual(logs, [["executing sql"]])
|
||||
}).pipe(
|
||||
Effect.provide(MysqlContainer.layerClient.pipe(
|
||||
Layer.provideMerge(SqlLogger)
|
||||
)),
|
||||
Effect.catchTag("ContainerError", () => Effect.void)
|
||||
), { timeout: 60_000 })
|
||||
})
|
||||
16
repos/effect/packages/sql/mysql2/test/KeyValueStore.test.ts
Normal file
16
repos/effect/packages/sql/mysql2/test/KeyValueStore.test.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Layer } from "effect"
|
||||
import * as KeyValueStoreTest from "effect-test/unstable/persistence/KeyValueStoreTest"
|
||||
import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore"
|
||||
import { MysqlContainer } from "./utils.ts"
|
||||
|
||||
KeyValueStoreTest.suite(
|
||||
"sql-mysql2",
|
||||
KeyValueStore.layerSql().pipe(Layer.provide(MysqlContainer.layerClient))
|
||||
)
|
||||
|
||||
KeyValueStoreTest.suite(
|
||||
"sql-mysql2-custom-table",
|
||||
KeyValueStore.layerSql({ table: "effect_key_value_store_custom" }).pipe(
|
||||
Layer.provide(MysqlContainer.layerClient)
|
||||
)
|
||||
)
|
||||
227
repos/effect/packages/sql/mysql2/test/Model.test.ts
Normal file
227
repos/effect/packages/sql/mysql2/test/Model.test.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { Cause, Effect, Schema } from "effect"
|
||||
import { Model } from "effect/unstable/schema"
|
||||
import { SqlClient, SqlModel, SqlResolver } from "effect/unstable/sql"
|
||||
import { MysqlContainer } from "./utils.ts"
|
||||
|
||||
class User extends Model.Class<User>("User")({
|
||||
id: Schema.Int.pipe(
|
||||
Model.FieldExcept(["insert"])
|
||||
),
|
||||
name: Schema.String,
|
||||
age: Schema.Int
|
||||
}) {}
|
||||
|
||||
class SoftDeleteUser extends Model.Class<SoftDeleteUser>("SoftDeleteUser")({
|
||||
id: Schema.Int.pipe(
|
||||
Model.FieldExcept(["insert"])
|
||||
),
|
||||
name: Schema.String,
|
||||
deletedAt: Schema.NullOr(Schema.String).pipe(
|
||||
Model.FieldOnly(["select", "update"])
|
||||
)
|
||||
}) {}
|
||||
|
||||
describe("SqlModel", () => {
|
||||
it.effect("insert returns result", () =>
|
||||
Effect.gen(function*() {
|
||||
const repo = yield* SqlModel.makeRepository(User, {
|
||||
tableName: "users",
|
||||
idColumn: "id",
|
||||
spanPrefix: "UserRepository"
|
||||
})
|
||||
const sql = yield* SqlClient.SqlClient
|
||||
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
||||
|
||||
const result = yield* repo.insert(User.insert.make({ name: "Alice", age: 30 }))
|
||||
assert.deepStrictEqual(result, new User({ id: 1, name: "Alice", age: 30 }))
|
||||
}).pipe(
|
||||
Effect.provide(MysqlContainer.layerClient),
|
||||
Effect.catchTag("ContainerError", () => Effect.void)
|
||||
), { timeout: 60_000 })
|
||||
|
||||
it.effect("insert returns result with transforms", () =>
|
||||
Effect.gen(function*() {
|
||||
const repo = yield* SqlModel.makeRepository(User, {
|
||||
tableName: "users",
|
||||
idColumn: "id",
|
||||
spanPrefix: "UserRepository"
|
||||
})
|
||||
const sql = yield* SqlClient.SqlClient
|
||||
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
||||
|
||||
const result = yield* repo.insert(User.insert.make({ name: "Alice", age: 30 }))
|
||||
assert.deepStrictEqual(result, new User({ id: 1, name: "Alice", age: 30 }))
|
||||
}).pipe(
|
||||
Effect.provide(MysqlContainer.layerClientWithTransforms),
|
||||
Effect.catchTag("ContainerError", () => Effect.void)
|
||||
), { timeout: 60_000 })
|
||||
|
||||
it.effect("insertVoid", () =>
|
||||
Effect.gen(function*() {
|
||||
const repo = yield* SqlModel.makeRepository(User, {
|
||||
tableName: "users",
|
||||
idColumn: "id",
|
||||
spanPrefix: "UserRepository"
|
||||
})
|
||||
const sql = yield* SqlClient.SqlClient
|
||||
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
||||
|
||||
const result = yield* repo.insertVoid(User.insert.make({ name: "Alice", age: 30 }))
|
||||
assert.strictEqual(result, void 0)
|
||||
}).pipe(
|
||||
Effect.provide(MysqlContainer.layerClient),
|
||||
Effect.catchTag("ContainerError", () => Effect.void)
|
||||
), { timeout: 60_000 })
|
||||
|
||||
it.live("insert data loader returns result", () =>
|
||||
Effect.gen(function*() {
|
||||
const repo = yield* SqlModel.makeResolvers(User, {
|
||||
tableName: "users",
|
||||
idColumn: "id",
|
||||
spanPrefix: "UserRepository"
|
||||
})
|
||||
const sql = yield* SqlClient.SqlClient
|
||||
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
||||
|
||||
const [alice, john] = yield* Effect.all([
|
||||
SqlResolver.request(User.insert.make({ name: "Alice", age: 30 }), repo.insert),
|
||||
SqlResolver.request(User.insert.make({ name: "John", age: 30 }), repo.insert)
|
||||
], { concurrency: "unbounded" })
|
||||
assert.deepStrictEqual(alice.name, "Alice")
|
||||
assert.deepStrictEqual(john.name, "John")
|
||||
}).pipe(
|
||||
Effect.provide(MysqlContainer.layerClient),
|
||||
Effect.catchTag("ContainerError", () => Effect.void)
|
||||
), { timeout: 60_000 })
|
||||
|
||||
it.live("findById data loader", () =>
|
||||
Effect.gen(function*() {
|
||||
const repo = yield* SqlModel.makeResolvers(User, {
|
||||
tableName: "users",
|
||||
idColumn: "id",
|
||||
spanPrefix: "UserRepository"
|
||||
})
|
||||
const sql = yield* SqlClient.SqlClient
|
||||
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
||||
const alice = yield* SqlResolver.request(User.insert.make({ name: "Alice", age: 30 }), repo.insert)
|
||||
const john = yield* SqlResolver.request(User.insert.make({ name: "John", age: 30 }), repo.insert)
|
||||
|
||||
const [alice2, john2] = yield* Effect.all([
|
||||
SqlResolver.request(alice.id, repo.findById),
|
||||
SqlResolver.request(john.id, repo.findById)
|
||||
], { concurrency: "unbounded" })
|
||||
|
||||
assert.deepStrictEqual(alice2.name, "Alice")
|
||||
assert.deepStrictEqual(john2.name, "John")
|
||||
}).pipe(
|
||||
Effect.provide(MysqlContainer.layerClient),
|
||||
Effect.catchTag("ContainerError", () => Effect.void)
|
||||
), { timeout: 60_000 })
|
||||
|
||||
it.effect("findById ignores soft deleted rows", () =>
|
||||
Effect.gen(function*() {
|
||||
const repo = yield* SqlModel.makeRepository(SoftDeleteUser, {
|
||||
tableName: "soft_delete_users",
|
||||
idColumn: "id",
|
||||
spanPrefix: "SoftDeleteUserRepository",
|
||||
softDeleteColumn: "deletedAt"
|
||||
})
|
||||
const sql = yield* SqlClient.SqlClient
|
||||
yield* sql`CREATE TABLE soft_delete_users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), deletedAt VARCHAR(255) NULL)`
|
||||
|
||||
const alice = yield* repo.insert(SoftDeleteUser.insert.make({ name: "Alice" }))
|
||||
const bob = yield* repo.insert(SoftDeleteUser.insert.make({ name: "Bob" }))
|
||||
yield* sql`UPDATE soft_delete_users SET deletedAt = CURRENT_TIMESTAMP WHERE id = ${bob.id}`
|
||||
|
||||
const aliceResult = yield* repo.findById(alice.id)
|
||||
assert.deepStrictEqual(aliceResult, new SoftDeleteUser({ id: alice.id, name: "Alice", deletedAt: null }))
|
||||
|
||||
const error = yield* Effect.flip(repo.findById(bob.id))
|
||||
assert.isTrue(Cause.isNoSuchElementError(error))
|
||||
|
||||
yield* repo.delete(alice.id)
|
||||
const rows = yield* sql<
|
||||
{ deletedAt: string | null }
|
||||
>`SELECT deletedAt FROM soft_delete_users WHERE id = ${alice.id}`
|
||||
assert.strictEqual(rows.length, 1)
|
||||
assert.isNotNull(rows[0]!.deletedAt)
|
||||
|
||||
const deletedError = yield* Effect.flip(repo.findById(alice.id))
|
||||
assert.isTrue(Cause.isNoSuchElementError(deletedError))
|
||||
}).pipe(
|
||||
Effect.provide(MysqlContainer.layerClient),
|
||||
Effect.catchTag("ContainerError", () => Effect.void)
|
||||
), { timeout: 60_000 })
|
||||
|
||||
it.live("findById data loader ignores soft deleted rows", () =>
|
||||
Effect.gen(function*() {
|
||||
const repo = yield* SqlModel.makeResolvers(SoftDeleteUser, {
|
||||
tableName: "soft_delete_user_resolvers",
|
||||
idColumn: "id",
|
||||
spanPrefix: "SoftDeleteUserRepository",
|
||||
softDeleteColumn: "deletedAt"
|
||||
})
|
||||
const sql = yield* SqlClient.SqlClient
|
||||
yield* sql`CREATE TABLE soft_delete_user_resolvers (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), deletedAt VARCHAR(255) NULL)`
|
||||
|
||||
const alice = yield* SqlResolver.request(SoftDeleteUser.insert.make({ name: "Alice" }), repo.insert)
|
||||
const bob = yield* SqlResolver.request(SoftDeleteUser.insert.make({ name: "Bob" }), repo.insert)
|
||||
yield* sql`UPDATE soft_delete_user_resolvers SET deletedAt = CURRENT_TIMESTAMP WHERE id = ${bob.id}`
|
||||
|
||||
const aliceResult = yield* SqlResolver.request(alice.id, repo.findById)
|
||||
assert.deepStrictEqual(aliceResult, new SoftDeleteUser({ id: alice.id, name: "Alice", deletedAt: null }))
|
||||
|
||||
const error = yield* Effect.flip(SqlResolver.request(bob.id, repo.findById))
|
||||
assert.isTrue(Cause.isNoSuchElementError(error))
|
||||
|
||||
yield* SqlResolver.request(alice.id, repo.delete)
|
||||
const rows = yield* sql<
|
||||
{ deletedAt: string | null }
|
||||
>`SELECT deletedAt FROM soft_delete_user_resolvers WHERE id = ${alice.id}`
|
||||
assert.strictEqual(rows.length, 1)
|
||||
assert.isNotNull(rows[0]!.deletedAt)
|
||||
|
||||
const deletedError = yield* Effect.flip(SqlResolver.request(alice.id, repo.findById))
|
||||
assert.isTrue(Cause.isNoSuchElementError(deletedError))
|
||||
}).pipe(
|
||||
Effect.provide(MysqlContainer.layerClient),
|
||||
Effect.catchTag("ContainerError", () => Effect.void)
|
||||
), { timeout: 60_000 })
|
||||
|
||||
it.effect("update returns result", () =>
|
||||
Effect.gen(function*() {
|
||||
const repo = yield* SqlModel.makeRepository(User, {
|
||||
tableName: "users",
|
||||
idColumn: "id",
|
||||
spanPrefix: "UserRepository"
|
||||
})
|
||||
const sql = yield* SqlClient.SqlClient
|
||||
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
||||
|
||||
let result = yield* repo.insert(User.insert.make({ name: "Alice", age: 30 }))
|
||||
result = yield* repo.update(User.update.make({ ...result, name: "Bob" }))
|
||||
assert.deepStrictEqual(result, new User({ id: 1, name: "Bob", age: 30 }))
|
||||
}).pipe(
|
||||
Effect.provide(MysqlContainer.layerClient),
|
||||
Effect.catchTag("ContainerError", () => Effect.void)
|
||||
), { timeout: 60_000 })
|
||||
|
||||
it.effect("update returns result with transforms", () =>
|
||||
Effect.gen(function*() {
|
||||
const repo = yield* SqlModel.makeRepository(User, {
|
||||
tableName: "users",
|
||||
idColumn: "id",
|
||||
spanPrefix: "UserRepository"
|
||||
})
|
||||
const sql = yield* SqlClient.SqlClient
|
||||
yield* sql`CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`
|
||||
|
||||
let result = yield* repo.insert(User.insert.make({ name: "Alice", age: 30 }))
|
||||
result = yield* repo.update(User.update.make({ ...result, name: "Bob" }))
|
||||
assert.deepStrictEqual(result, new User({ id: 1, name: "Bob", age: 30 }))
|
||||
}).pipe(
|
||||
Effect.provide(MysqlContainer.layerClientWithTransforms),
|
||||
Effect.catchTag("ContainerError", () => Effect.void)
|
||||
), { timeout: 60_000 })
|
||||
})
|
||||
35
repos/effect/packages/sql/mysql2/test/MysqlClient.test.ts
Normal file
35
repos/effect/packages/sql/mysql2/test/MysqlClient.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { MysqlClient } from "@effect/sql-mysql2"
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { Effect, Stream } from "effect"
|
||||
|
||||
import { MysqlContainer } from "./utils.ts"
|
||||
|
||||
describe("MysqlClient", () => {
|
||||
it.effect("stream returns same rows as direct execution", () =>
|
||||
Effect.gen(function*() {
|
||||
const sql = yield* MysqlClient.MysqlClient
|
||||
|
||||
yield* sql`DROP TABLE IF EXISTS stream_regression_test`
|
||||
yield* sql`CREATE TABLE stream_regression_test (id INT PRIMARY KEY AUTO_INCREMENT, value VARCHAR(255))`
|
||||
|
||||
const testData = Array.from({ length: 100 }, (_, i) => ({ value: `row_${i}` }))
|
||||
yield* sql`INSERT INTO stream_regression_test ${sql.insert(testData)}`
|
||||
|
||||
const directResults = yield* sql`SELECT * FROM stream_regression_test`
|
||||
|
||||
const streamResults = yield* Stream.runCollect(
|
||||
sql`SELECT * FROM stream_regression_test`.stream
|
||||
)
|
||||
|
||||
yield* sql`DROP TABLE stream_regression_test`
|
||||
|
||||
assert.strictEqual(
|
||||
streamResults.length,
|
||||
directResults.length,
|
||||
`Stream returned ${streamResults.length} rows, expected ${directResults.length}`
|
||||
)
|
||||
}).pipe(
|
||||
Effect.provide(MysqlContainer.layerClient),
|
||||
Effect.catchTag("ContainerError", () => Effect.void)
|
||||
), { timeout: 60_000 })
|
||||
})
|
||||
22
repos/effect/packages/sql/mysql2/test/Persistence.test.ts
Normal file
22
repos/effect/packages/sql/mysql2/test/Persistence.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Layer } from "effect"
|
||||
import * as PersistedCacheTest from "effect-test/unstable/persistence/PersistedCacheTest"
|
||||
import * as PersistedQueueTest from "effect-test/unstable/persistence/PersistedQueueTest"
|
||||
import { PersistedQueue, Persistence } from "effect/unstable/persistence"
|
||||
import { MysqlContainer } from "./utils.ts"
|
||||
|
||||
PersistedCacheTest.suite(
|
||||
"sql-mysql2-multi",
|
||||
Persistence.layerSqlMultiTable.pipe(Layer.provide(MysqlContainer.layerClient))
|
||||
)
|
||||
|
||||
PersistedCacheTest.suite(
|
||||
"sql-mysql2-single",
|
||||
Persistence.layerSql.pipe(Layer.provide(MysqlContainer.layerClient))
|
||||
)
|
||||
|
||||
PersistedQueueTest.suite(
|
||||
"sql-mysql2",
|
||||
PersistedQueue.layerStoreSql().pipe(
|
||||
Layer.provide(MysqlContainer.layerClient)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,133 @@
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { Effect } from "effect"
|
||||
import * as Reactivity from "effect/unstable/reactivity/Reactivity"
|
||||
import type * as SqlError from "effect/unstable/sql/SqlError"
|
||||
import { vi } from "vitest"
|
||||
|
||||
const state: {
|
||||
connectCause: unknown
|
||||
} = {
|
||||
connectCause: null
|
||||
}
|
||||
|
||||
vi.mock("mysql2", () => ({
|
||||
createPool: () => ({
|
||||
query: (_sql: string, cb: (cause: unknown) => void) => cb(state.connectCause),
|
||||
end: (cb: () => void) => cb(),
|
||||
getConnection: (_cb: () => void) => undefined
|
||||
})
|
||||
}))
|
||||
|
||||
const connectFailureReason = (cause: unknown) =>
|
||||
Effect.gen(function*() {
|
||||
state.connectCause = cause
|
||||
const { MysqlClient } = yield* Effect.promise(() => import("@effect/sql-mysql2"))
|
||||
const error = yield* Effect.flip(MysqlClient.make({}))
|
||||
return error.reason
|
||||
}).pipe(
|
||||
Effect.scoped,
|
||||
Effect.provide(Reactivity.layer)
|
||||
)
|
||||
|
||||
const connectFailureReasonTag = (errno: number) => Effect.map(connectFailureReason({ errno }), (reason) => reason._tag)
|
||||
|
||||
const assertUniqueViolation = (reason: SqlError.SqlErrorReason, constraint: string) => {
|
||||
assert.strictEqual(reason._tag, "UniqueViolation")
|
||||
if (reason._tag === "UniqueViolation") {
|
||||
assert.strictEqual(reason.constraint, constraint)
|
||||
}
|
||||
}
|
||||
|
||||
describe("MysqlClient SqlError classification", () => {
|
||||
it.effect("maps representative errno codes to reasons", () =>
|
||||
Effect.gen(function*() {
|
||||
const cases = [
|
||||
[1040, "ConnectionError"],
|
||||
[1045, "AuthenticationError"],
|
||||
[1142, "AuthorizationError"],
|
||||
[1064, "SqlSyntaxError"],
|
||||
[1062, "UniqueViolation"],
|
||||
[1048, "ConstraintError"],
|
||||
[1213, "DeadlockError"],
|
||||
[1205, "LockTimeoutError"],
|
||||
[3024, "StatementTimeoutError"]
|
||||
] as const
|
||||
|
||||
for (const [errno, expectedTag] of cases) {
|
||||
const tag = yield* connectFailureReasonTag(errno)
|
||||
assert.strictEqual(tag, expectedTag)
|
||||
}
|
||||
}))
|
||||
|
||||
it.effect("falls back to UnknownError for unmapped errno", () =>
|
||||
Effect.gen(function*() {
|
||||
const tag = yield* connectFailureReasonTag(9999)
|
||||
assert.strictEqual(tag, "UnknownError")
|
||||
}))
|
||||
|
||||
it.effect("classifies duplicate-entry errno 1062 as UniqueViolation", () =>
|
||||
Effect.gen(function*() {
|
||||
const reason = yield* connectFailureReason({ errno: 1062 })
|
||||
assertUniqueViolation(reason, "unknown")
|
||||
}))
|
||||
|
||||
it.effect("extracts duplicate-entry constraints from structured fields and common messages", () =>
|
||||
Effect.gen(function*() {
|
||||
const explicit = yield* connectFailureReason({
|
||||
errno: 1062,
|
||||
constraint: " users_email_key ",
|
||||
sqlMessage: "Duplicate entry 'user@example.com' for key 'ignored_key'"
|
||||
})
|
||||
assertUniqueViolation(explicit, "users_email_key")
|
||||
|
||||
const singleQuoted = yield* connectFailureReason({
|
||||
errno: 1062,
|
||||
sqlMessage: "Duplicate entry 'user@example.com' for key 'users_email_key'"
|
||||
})
|
||||
assertUniqueViolation(singleQuoted, "users_email_key")
|
||||
|
||||
const backtickQuoted = yield* connectFailureReason({
|
||||
errno: 1062,
|
||||
message: "Duplicate entry '1' for key `PRIMARY`"
|
||||
})
|
||||
assertUniqueViolation(backtickQuoted, "PRIMARY")
|
||||
|
||||
const unquoted = yield* connectFailureReason({
|
||||
errno: 1062,
|
||||
sqlMessage: "Duplicate entry 'user@example.com' for key users_email_key"
|
||||
})
|
||||
assertUniqueViolation(unquoted, "users_email_key")
|
||||
}))
|
||||
|
||||
it.effect("uses unknown for blank, missing, malformed, or non-string duplicate-entry metadata", () =>
|
||||
Effect.gen(function*() {
|
||||
const missing = yield* connectFailureReason({ errno: 1062 })
|
||||
assertUniqueViolation(missing, "unknown")
|
||||
|
||||
const blank = yield* connectFailureReason({
|
||||
errno: 1062,
|
||||
constraint: " ",
|
||||
sqlMessage: "Duplicate entry 'user@example.com' for key ' '"
|
||||
})
|
||||
assertUniqueViolation(blank, "unknown")
|
||||
|
||||
const malformed = yield* connectFailureReason({
|
||||
errno: 1062,
|
||||
sqlMessage: "Duplicate entry 'user@example.com'"
|
||||
})
|
||||
assertUniqueViolation(malformed, "unknown")
|
||||
|
||||
const nonString = yield* connectFailureReason({
|
||||
errno: 1062,
|
||||
sqlMessage: 1062,
|
||||
message: { text: "Duplicate entry 'user@example.com' for key 'users_email_key'" }
|
||||
})
|
||||
assertUniqueViolation(nonString, "unknown")
|
||||
}))
|
||||
|
||||
it.effect("keeps non-unique constraint errno values classified as ConstraintError", () =>
|
||||
Effect.gen(function*() {
|
||||
const tag = yield* connectFailureReasonTag(1452)
|
||||
assert.strictEqual(tag, "ConstraintError")
|
||||
}))
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
import * as SqlEventLogServerUnencryptedStorageTest from "effect-test/unstable/eventlog/SqlEventLogServerUnencryptedStorageTest"
|
||||
import { MysqlContainer } from "./utils.ts"
|
||||
|
||||
SqlEventLogServerUnencryptedStorageTest.suite(
|
||||
"sql-mysql2",
|
||||
MysqlContainer.layerClient
|
||||
)
|
||||
64
repos/effect/packages/sql/mysql2/test/utils.ts
Normal file
64
repos/effect/packages/sql/mysql2/test/utils.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { MysqlClient } from "@effect/sql-mysql2"
|
||||
import type { StartedMySqlContainer } from "@testcontainers/mysql"
|
||||
import { MySqlContainer } from "@testcontainers/mysql"
|
||||
import { Context, Data, Effect, Layer, Redacted, String } from "effect"
|
||||
|
||||
export class ContainerError extends Data.TaggedError("ContainerError")<{
|
||||
cause: unknown
|
||||
}> {}
|
||||
|
||||
export class MysqlContainer extends Context.Service<
|
||||
MysqlContainer,
|
||||
StartedMySqlContainer
|
||||
>()("test/MysqlContainer") {
|
||||
static readonly layer = Layer.effect(this)(
|
||||
Effect.acquireRelease(
|
||||
Effect.tryPromise({
|
||||
try: () => new MySqlContainer("mysql:lts").start(),
|
||||
catch: (cause) => new ContainerError({ cause })
|
||||
}),
|
||||
(container) => Effect.promise(() => container.stop())
|
||||
)
|
||||
)
|
||||
|
||||
static client = Layer.unwrap(
|
||||
Effect.gen(function*() {
|
||||
const container = yield* MysqlContainer
|
||||
return MysqlClient.layer({
|
||||
url: Redacted.make(container.getConnectionUri())
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
static layerClient = this.client.pipe(Layer.provide(this.layer))
|
||||
|
||||
static layerClientWithTransforms = Layer.unwrap(
|
||||
Effect.gen(function*() {
|
||||
const container = yield* MysqlContainer
|
||||
return MysqlClient.layer({
|
||||
url: Redacted.make(container.getConnectionUri()),
|
||||
transformQueryNames: String.camelToSnake,
|
||||
transformResultNames: String.snakeToCamel
|
||||
})
|
||||
})
|
||||
).pipe(Layer.provide(this.layer))
|
||||
|
||||
static layerVitest = Layer.effect(
|
||||
this,
|
||||
Effect.acquireRelease(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
new MySqlContainer("vitess/vttestserver:mysql80").withEnvironment({
|
||||
KEYSPACES: "test,unsharded",
|
||||
NUM_SHARDS: "1,1",
|
||||
MYSQL_BIND_HOST: "0.0.0.0",
|
||||
PORT: "3303"
|
||||
}).start(),
|
||||
catch: (cause) => new ContainerError({ cause })
|
||||
}),
|
||||
(container) => Effect.promise(() => container.stop())
|
||||
)
|
||||
)
|
||||
|
||||
static layerClientVitess = this.client.pipe(Layer.provide(this.layerVitest))
|
||||
}
|
||||
Reference in New Issue
Block a user