sqlite.test.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import { mkdir, mkdtemp, rm } from "node:fs/promises"
  2. import { tmpdir } from "node:os"
  3. import { join } from "node:path"
  4. import { expect, test } from "bun:test"
  5. import { SqliteClient } from "@effect/sql-sqlite-bun"
  6. import { eq, sql } from "drizzle-orm"
  7. import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
  8. import { Effect } from "effect"
  9. import type { SqlClient as SqlClientService } from "effect/unstable/sql/SqlClient"
  10. import { EffectDrizzleSqlite } from "../src"
  11. const users = sqliteTable("users", {
  12. id: integer().primaryKey({ autoIncrement: true }),
  13. name: text().notNull(),
  14. })
  15. const run = <A, E>(effect: Effect.Effect<A, E, SqlClientService>) =>
  16. Effect.runPromise(
  17. effect.pipe(Effect.provide(SqliteClient.layer({ filename: ":memory:", disableWAL: true })), Effect.scoped),
  18. )
  19. const makeDb = Effect.gen(function* () {
  20. const db = yield* EffectDrizzleSqlite.makeWithDefaults()
  21. yield* db.run(sql`create table users (id integer primary key autoincrement, name text not null)`)
  22. return db
  23. })
  24. const createMigrationsFolder = async () => {
  25. const migrationsFolder = await mkdtemp(join(tmpdir(), "effect-drizzle-sqlite-"))
  26. await mkdir(join(migrationsFolder, "20240101000000_create_migrated_users"), { recursive: true })
  27. await Bun.write(
  28. join(migrationsFolder, "20240101000000_create_migrated_users", "migration.sql"),
  29. "create table migrated_users (id integer primary key autoincrement, name text not null);",
  30. )
  31. return migrationsFolder
  32. }
  33. test("selects rows through Effect-yieldable query builders", async () => {
  34. await run(
  35. Effect.gen(function* () {
  36. const db = yield* makeDb
  37. yield* db.insert(users).values({ name: "Ada" })
  38. expect(yield* db.select().from(users)).toEqual([{ id: 1, name: "Ada" }])
  39. expect(yield* db.select({ id: users.id }).from(users).where(eq(users.name, "Ada")).get()).toEqual({ id: 1 })
  40. }),
  41. )
  42. })
  43. test("commits successful transactions", async () => {
  44. await run(
  45. Effect.gen(function* () {
  46. const db = yield* makeDb
  47. yield* db.transaction((tx) => tx.insert(users).values({ name: "Grace" }), { behavior: "immediate" })
  48. expect(yield* db.select().from(users)).toEqual([{ id: 1, name: "Grace" }])
  49. }),
  50. )
  51. })
  52. test("rolls back failed transactions", async () => {
  53. await run(
  54. Effect.gen(function* () {
  55. const db = yield* makeDb
  56. yield* db
  57. .transaction((tx) =>
  58. tx
  59. .insert(users)
  60. .values({ name: "Linus" })
  61. .pipe(Effect.andThen(Effect.fail("boom"))),
  62. )
  63. .pipe(Effect.ignore)
  64. expect(yield* db.select().from(users)).toEqual([])
  65. }),
  66. )
  67. })
  68. test("rolls back explicit transaction rollback", async () => {
  69. await run(
  70. Effect.gen(function* () {
  71. const db = yield* makeDb
  72. yield* db
  73. .transaction((tx) =>
  74. tx
  75. .insert(users)
  76. .values({ name: "Barbara" })
  77. .pipe(Effect.andThen(Effect.fail(tx.rollback()))),
  78. )
  79. .pipe(Effect.ignore)
  80. expect(yield* db.select().from(users)).toEqual([])
  81. }),
  82. )
  83. })
  84. test("supports returning and rejects empty update sets", async () => {
  85. await run(
  86. Effect.gen(function* () {
  87. const db = yield* makeDb
  88. const inserted = yield* db.insert(users).values({ name: "Ada" }).returning({ id: users.id, name: users.name })
  89. expect(inserted).toEqual([{ id: 1, name: "Ada" }])
  90. const updated = yield* db.update(users).set({ name: "Grace" }).where(eq(users.id, 1)).returning()
  91. expect(updated).toEqual([{ id: 1, name: "Grace" }])
  92. const deleted = yield* db.delete(users).where(eq(users.id, 1)).returning({ id: users.id })
  93. expect(deleted).toEqual([{ id: 1 }])
  94. expect(() => db.update(users).set({ name: undefined })).toThrow("No values to set")
  95. }),
  96. )
  97. })
  98. test("runs migrations once and records migration metadata", async () => {
  99. const migrationsFolder = await createMigrationsFolder()
  100. try {
  101. await run(
  102. Effect.gen(function* () {
  103. const db = yield* EffectDrizzleSqlite.makeWithDefaults()
  104. yield* EffectDrizzleSqlite.migrate(db, { migrationsFolder })
  105. yield* EffectDrizzleSqlite.migrate(db, { migrationsFolder })
  106. yield* db.run(sql`insert into migrated_users (name) values ('Margaret')`)
  107. expect(yield* db.all<{ name: string }>(sql`select name from migrated_users`)).toEqual([{ name: "Margaret" }])
  108. expect(yield* db.all<{ name: string | null }>(sql`select name from __drizzle_migrations`)).toEqual([
  109. { name: "20240101000000_create_migrated_users" },
  110. ])
  111. }),
  112. )
  113. } finally {
  114. await rm(migrationsFolder, { recursive: true, force: true })
  115. }
  116. })