session.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /* oxlint-disable */
  2. import * as Context from "effect/Context"
  3. import * as Effect from "effect/Effect"
  4. import * as Exit from "effect/Exit"
  5. import * as Scope from "effect/Scope"
  6. import type { SqlClient } from "effect/unstable/sql/SqlClient"
  7. import type { SqlError } from "effect/unstable/sql/SqlError"
  8. import type { EffectCacheShape } from "drizzle-orm/cache/core/cache-effect"
  9. import type { WithCacheConfig } from "drizzle-orm/cache/core/types"
  10. import type { EffectDrizzleQueryError } from "drizzle-orm/effect-core/errors"
  11. import type { EffectLoggerShape } from "drizzle-orm/effect-core/logger"
  12. import type { QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect"
  13. import { entityKind } from "drizzle-orm/entity"
  14. import type { AnyRelations } from "drizzle-orm/relations"
  15. import type { RelationalQueryMapperConfig } from "drizzle-orm/relations"
  16. import type { Query } from "drizzle-orm/sql/sql"
  17. import type { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core/dialect"
  18. import { SQLiteEffectPreparedQuery, SQLiteEffectSession, SQLiteEffectTransaction } from "../sqlite-core/effect/session"
  19. import type { SelectedFieldsOrdered } from "drizzle-orm/sqlite-core/query-builders/select.types"
  20. import type { PreparedQueryConfig, SQLiteExecuteMethod, SQLiteTransactionConfig } from "drizzle-orm/sqlite-core/session"
  21. export interface EffectSQLiteQueryEffectHKT extends QueryEffectHKTBase {
  22. readonly error: EffectDrizzleQueryError
  23. readonly context: never
  24. }
  25. export type EffectSQLiteRunResult = readonly never[]
  26. export interface EffectSQLiteSessionOptions {
  27. logger: EffectLoggerShape
  28. cache: EffectCacheShape
  29. useJitMappers?: boolean
  30. }
  31. export class EffectSQLiteSession<TRelations extends AnyRelations> extends SQLiteEffectSession<
  32. EffectSQLiteQueryEffectHKT,
  33. EffectSQLiteRunResult,
  34. TRelations
  35. > {
  36. static override readonly [entityKind]: string = "EffectSQLiteSession"
  37. constructor(
  38. private client: SqlClient,
  39. dialect: SQLiteAsyncDialect,
  40. protected relations: TRelations,
  41. private options: EffectSQLiteSessionOptions,
  42. ) {
  43. super(dialect)
  44. }
  45. override prepareQuery<T extends PreparedQueryConfig = PreparedQueryConfig>(
  46. query: Query,
  47. fields: SelectedFieldsOrdered | undefined,
  48. executeMethod: SQLiteExecuteMethod,
  49. customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown,
  50. queryMetadata?: {
  51. type: "select" | "update" | "delete" | "insert"
  52. tables: string[]
  53. },
  54. cacheConfig?: WithCacheConfig,
  55. ): SQLiteEffectPreparedQuery<T, EffectSQLiteQueryEffectHKT> {
  56. return new SQLiteEffectPreparedQuery<T, EffectSQLiteQueryEffectHKT>(
  57. (params, method) => this.execute(query, params, method),
  58. query,
  59. this.options.logger,
  60. this.options.cache,
  61. queryMetadata,
  62. cacheConfig,
  63. fields,
  64. executeMethod,
  65. this.options.useJitMappers,
  66. customResultMapper,
  67. undefined,
  68. undefined,
  69. this.isInTransaction(),
  70. )
  71. }
  72. override prepareRelationalQuery<T extends PreparedQueryConfig = PreparedQueryConfig>(
  73. query: Query,
  74. fields: SelectedFieldsOrdered | undefined,
  75. executeMethod: SQLiteExecuteMethod,
  76. customResultMapper: (rows: Record<string, unknown>[], mapColumnValue?: (value: unknown) => unknown) => unknown,
  77. config: RelationalQueryMapperConfig,
  78. ): SQLiteEffectPreparedQuery<T, EffectSQLiteQueryEffectHKT, true> {
  79. return new SQLiteEffectPreparedQuery<T, EffectSQLiteQueryEffectHKT, true>(
  80. (params, method) => this.execute(query, params, method),
  81. query,
  82. this.options.logger,
  83. this.options.cache,
  84. undefined,
  85. undefined,
  86. fields,
  87. executeMethod,
  88. this.options.useJitMappers,
  89. customResultMapper,
  90. true,
  91. config,
  92. this.isInTransaction(),
  93. )
  94. }
  95. private execute(query: Query, params: unknown[], method: SQLiteExecuteMethod | "values") {
  96. const statement = this.client.unsafe(query.sql, params)
  97. if (method === "values") return statement.values
  98. if (method === "get") return statement.withoutTransform.pipe(Effect.map((rows) => rows[0]))
  99. return statement.withoutTransform
  100. }
  101. private isInTransaction() {
  102. return Effect.serviceOption(this.client.transactionService).pipe(Effect.map((option) => option._tag === "Some"))
  103. }
  104. private executeTransactionStatement(connection: Effect.Success<SqlClient["reserve"]>, query: string) {
  105. return connection.executeUnprepared(query, [], undefined).pipe(Effect.asVoid)
  106. }
  107. private withTransaction<A, E, R>(effect: Effect.Effect<A, E, R>, config: SQLiteTransactionConfig | undefined) {
  108. return Effect.uninterruptibleMask((restore) =>
  109. Effect.withFiber<A, E | SqlError, R>((fiber) => {
  110. const services = fiber.context
  111. const connectionOption = Context.getOption(services, this.client.transactionService)
  112. const connection: Effect.Effect<
  113. readonly [Scope.Closeable | undefined, Effect.Success<SqlClient["reserve"]>],
  114. SqlError
  115. > =
  116. connectionOption._tag === "Some"
  117. ? Effect.succeed([undefined, connectionOption.value[0]] as const)
  118. : Scope.make().pipe(
  119. Effect.flatMap((scope) =>
  120. Scope.provide(this.client.reserve, scope).pipe(
  121. Effect.map((connection) => [scope, connection] as const),
  122. Effect.catch((error) =>
  123. Scope.close(scope, Exit.fail(error)).pipe(Effect.andThen(Effect.fail(error))),
  124. ),
  125. ),
  126. ),
  127. )
  128. const id = connectionOption._tag === "Some" ? connectionOption.value[1] + 1 : 0
  129. return connection.pipe(
  130. Effect.flatMap(([scope, connection]) =>
  131. this.executeTransactionStatement(
  132. connection,
  133. id === 0 ? `begin ${config?.behavior ?? "deferred"}` : `savepoint effect_sql_${id}`,
  134. ).pipe(
  135. Effect.flatMap(() =>
  136. Effect.provideContext(
  137. restore(effect),
  138. Context.add(services, this.client.transactionService, [connection, id]),
  139. ),
  140. ),
  141. Effect.exit,
  142. Effect.flatMap((exit) => {
  143. const finalize = Exit.isSuccess(exit)
  144. ? id === 0
  145. ? this.executeTransactionStatement(connection, "commit").pipe(
  146. // SQLite keeps the transaction open after deferred constraint commit failures.
  147. Effect.catch((error) =>
  148. this.executeTransactionStatement(connection, "rollback").pipe(
  149. Effect.catch(() => Effect.void),
  150. Effect.andThen(Effect.fail(error)),
  151. ),
  152. ),
  153. )
  154. : this.executeTransactionStatement(connection, `release savepoint effect_sql_${id}`)
  155. : id === 0
  156. ? this.executeTransactionStatement(connection, "rollback")
  157. : this.executeTransactionStatement(connection, `rollback to savepoint effect_sql_${id}`).pipe(
  158. Effect.andThen(
  159. this.executeTransactionStatement(connection, `release savepoint effect_sql_${id}`),
  160. ),
  161. )
  162. const scoped = scope === undefined ? finalize : Effect.ensuring(finalize, Scope.close(scope, exit))
  163. return scoped.pipe(Effect.flatMap(() => exit))
  164. }),
  165. ),
  166. ),
  167. )
  168. }),
  169. )
  170. }
  171. override transaction<A, E, R>(
  172. transaction: (tx: EffectSQLiteTransaction<TRelations>) => Effect.Effect<A, E, R>,
  173. config?: SQLiteTransactionConfig,
  174. ): Effect.Effect<A, E | SqlError, R> {
  175. const { dialect, relations } = this
  176. return this.withTransaction(
  177. Effect.gen({ self: this }, function* () {
  178. const tx = new EffectSQLiteTransaction<TRelations>(dialect, this, relations)
  179. return yield* transaction(tx)
  180. }),
  181. config,
  182. )
  183. }
  184. }
  185. export class EffectSQLiteTransaction<TRelations extends AnyRelations> extends SQLiteEffectTransaction<
  186. EffectSQLiteQueryEffectHKT,
  187. EffectSQLiteRunResult,
  188. TRelations
  189. > {
  190. static override readonly [entityKind]: string = "EffectSQLiteTransaction"
  191. override transaction: <A, E, R>(
  192. transaction: (
  193. tx: SQLiteEffectTransaction<EffectSQLiteQueryEffectHKT, EffectSQLiteRunResult, TRelations>,
  194. ) => Effect.Effect<A, E, R>,
  195. ) => Effect.Effect<A, SqlError | E, R> = (tx) => this.session.transaction(tx)
  196. }