mcpField
P
prisma-driver-adapter-implementation

prisma-driver-adapter-implementation

github.com/prisma开发76383.2k 次安装2 天前更新
代码仓库

关于 prisma-driver-adapter-implementation

面向按已安装的 `@prisma/driver-adapter-utils` 精确版本实现 Prisma SQL 驱动适配器的开发者。重点涵盖事务生命周期、值与错误映射、资源所有权及影子数据库隔离。

主要功能

  1. 01适配器工厂与连接契约
  2. 02查询值及元数据映射
  3. 03事务与保存点生命周期
  4. 04数据库错误映射
  5. 05连接池所有权与影子数据库规则
  6. 06实现验证清单

适用场景

  1. 01实现 Prisma SQL 驱动适配器
  2. 02添加嵌套事务支持
  3. 03排查适配器错误或连接泄漏
  4. 04测试影子数据库行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/prisma/skills --skill prisma-driver-adapter-implementation

安装后在 Claude Code 中输入/prisma-driver-adapter-implementation确认已加载。

详细介绍

Prisma SQL 驱动适配器实现

请将本指南与目标 Prisma 版本中安装的确切 @prisma/driver-adapter-utils 版本配合使用。驱动适配器是协议边界:代码即使类型兼容,仍可能损坏值、泄漏连接或破坏事务。

适用场景

  • 实现 SqlDriverAdapterFactory、SqlMigrationAwareDriverAdapterFactory、SqlDriverAdapter 或 Transaction
  • 添加嵌套事务/保存点支持
  • 映射驱动值、列元数据、绑定参数或数据库错误
  • 调试 P2039、事务泄漏、影子数据库故障或适配器特有的查询行为

契约概览

interface SqlDriverAdapterFactory extends AdapterInfo {
  connect(): Promise<SqlDriverAdapter>
}

interface SqlMigrationAwareDriverAdapterFactory extends SqlDriverAdapterFactory {
  connectToShadowDb(): Promise<SqlDriverAdapter>
}

interface SqlDriverAdapter extends AdapterInfo {
  queryRaw(query: SqlQuery): Promise<SqlResultSet>
  executeRaw(query: SqlQuery): Promise<number>
  executeScript(script: string): Promise<void>
  startTransaction(isolationLevel?: IsolationLevel): Promise<Transaction>
  getConnectionInfo?(): ConnectionInfo
  dispose(): Promise<void>
}

interface Transaction extends AdapterInfo {
  readonly options: { usePhantomQuery: boolean }
  queryRaw(query: SqlQuery): Promise<SqlResultSet>
  executeRaw(query: SqlQuery): Promise<number>
  commit(): Promise<void>
  rollback(): Promise<void>
  createSavepoint?(name: string): Promise<void>
  rollbackToSavepoint?(name: string): Promise<void>
  releaseSavepoint?(name: string): Promise<void>
}

IsolationLevel 目前包含 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SNAPSHOT 和 SERIALIZABLE;请验证具体数据库支持哪些级别。

优先级规则

优先级规则影响
严重每个事务使用一个专用连接防止交错执行和连接泄漏
严重commit/rollback 是生命周期清理钩子防止重复执行 COMMIT/ROLLBACK
严重保存点属于 Transaction,而非适配器全局深度使嵌套作用域限定在各自连接内
严重保留原始数据库错误代码/消息便于使用 P2039 回退处理
高精确映射参数和结果元数据防止值在无提示的情况下损坏
高隔离影子数据库并始终清理确保 Migrate 安全
高仅释放适配器拥有的资源防止关闭调用方拥有的连接池

查询实现

SqlQuery 包含 sql、args 和与之平行的 argTypes。请结合值和 ArgType 映射每个参数;不要丢弃类型/数量信息。请使用驱动的数组/元组行模式执行,以确保列顺序稳定。

class ExampleQueryable {
  readonly provider = 'postgres' as const
  readonly adapterName = '@acme/adapter-example'

  constructor(protected readonly connection: DriverConnection) {}

  async queryRaw(query: SqlQuery): Promise<SqlResultSet> {
    try {
      const result = await this.connection.query({
        text: query.sql,
        values: query.args.map((value, index) =>
          mapArg(value, query.argTypes[index]),
        ),
        rowMode: 'array',
      })

      return {
        columnNames: result.fields.map((field) => field.name),
        columnTypes: result.fields.map(mapColumnType),
        rows: result.rows,
      }
    } catch (error) {
      throwAdapterError(error)
    }
  }

  async executeRaw(query: SqlQuery): Promise<number> {
    try {
      const result = await this.connection.execute(
        query.sql,
        query.args.map((value, index) => mapArg(value, query.argTypes[index])),
      )
      return result.rowsAffected ?? 0
    } catch (error) {
      throwAdapterError(error)
    }
  }
}
结果映射

返回的 columnNames、columnTypes 和 rows 必须长度相同且顺序一致。请谨慎将驱动元数据映射到 ColumnTypeEnum:

  • 将有符号整数宽度映射为 Int32/Int64;保留 64 位值,避免被 JS 数字截断
  • 将 decimal/numeric 映射为 Numeric,并采用 Prisma 期望的表示形式
  • 将二进制数据映射为 Uint8Array/Bytes
  • 将仅含日期、仅含时间和时间戳分别映射为 Date、Time 和 DateTime
  • 将 UUID、JSON、枚举、数组和提供商特有的未知值映射为其明确类型
  • 将不支持的原生类型映射为 DriverAdapterError({ kind: 'UnsupportedNativeDataType', type })

测试 null、空数组、数组元素类型、大整数、decimal、字节数组、JSON、日期以及用户定义/未知的原生类型。

脚本执行

executeScript 必须按照提供商的预期执行迁移脚本。优先使用驱动原生的多语句/脚本功能或真正的 SQL 解析器。简单地按 ; 拆分会破坏函数、触发器、带引号的字符串以及方言特有的代码块。

事务协议

startTransaction 必须获取一个专用连接、启动数据库事务、应用请求的隔离级别,并返回绑定到同一连接的 Transaction。如果设置失败,请立即释放连接。

async startTransaction(level?: IsolationLevel): Promise<Transaction> {
  const connection = await this.pool.acquire()
  try {
    const tx = new ExampleTransaction(connection, () => connection.release())
    await tx.executeRaw({ sql: 'BEGIN', args: [], argTypes: [] })
    if (level) {
      await tx.executeRaw({
        sql: `SET TRANSACTION ISOLATION LEVEL ${validateLevel(level)}`,
        args: [],
        argTypes: [],
      })
    }
    return tx
  } catch (error) {
    connection.release(error)
    throwAdapterError(error)
  }
}
提交和回滚

Prisma 通过 executeRaw 协调 SQL COMMIT/ROLLBACK。事务对象的 commit() 和 rollback() 方法是生命周期钩子:分离监听器,并且只释放一次专用连接。它们不得再次执行 SQL 提交/回滚。

class ExampleTransaction extends ExampleQueryable implements Transaction {
  readonly options = { usePhantomQuery: false }
  #closed = false

  constructor(connection: DriverConnection, private readonly release: () => void) {
    super(connection)
  }

  async commit() { this.finish() }
  async rollback() { this.finish() }

  private finish() {
    if (this.#closed) return
    this.#closed = true
    this.release()
  }

  async createSavepoint(name: string) {
    await this.control(`SAVEPOINT ${safeSavepoint(name)}`)
  }

  async rollbackToSavepoint(name: string) {
    await this.control(`ROLLBACK TO SAVEPOINT ${safeSavepoint(name)}`)
  }

  async releaseSavepoint(name: string) {
    await this.control(`RELEASE SAVEPOINT ${safeSavepoint(name)}`)
  }

  private async control(sql: string) {
    await this.executeRaw({ sql, args: [], argTypes: [] })
  }
}

仅在提供商支持时实现可选的保存点方法。请验证/引用保存点标识符。对于保存点有意设为无操作的提供商,请记录并测试此限制。

切勿在共享适配器上保存事务深度。并行事务会导致适配器全局深度不正确;嵌套状态应归属于返回的事务连接和 Prisma 的保存点调用。

错误映射

将识别出的驱动程序故障封装在 DriverAdapterError 中。将已知情况映射到 MappedError 类型,例如约束冲突、身份验证/可达性问题、表/列/数据库不存在、超时、事务已关闭、输入无效、值超出范围和写入冲突。

对于数据库错误,即使回退到提供程序特定的原始变体,也要保留 originalCode 和 originalMessage:

import {
  DriverAdapterError,
  type Error as DriverAdapterErrorObject,
  type MappedError,
} from '@prisma/driver-adapter-utils'

function convertDriverError(error: DatabaseError): DriverAdapterErrorObject {
  return {
    originalCode: String(error.code),
    originalMessage: error.message,
    ...mapKnownOrRaw(error),
  }
}

function mapKnownOrRaw(error: DatabaseError): MappedError {
  if (error.code === '23505') {
    return { kind: 'UniqueConstraintViolation', constraint: parsedConstraint(error) }
  }
  return {
    kind: 'postgres',
    code: String(error.code ?? 'N/A'),
    severity: error.severity ?? 'N/A',
    message: error.message,
    detail: error.detail,
    column: error.column,
    hint: error.hint,
  }
}

function throwAdapterError(error: unknown): never {
  if (!isDatabaseError(error)) throw error
  throw new DriverAdapterError(convertDriverError(error))
}

当未映射的驱动程序错误变为 P2039 时,Prisma 会使用保留的原始详细信息。不要将每个未知异常都替换为伪造的 GenericJs id;应重新抛出真正意外的非驱动程序错误,以便编程错误仍然可见。

工厂、所有权和影子数据库

  • connect() 返回一个全新且可用的适配器连接/连接池包装器。
  • 跟踪连接池是否由工厂创建。dispose() 会关闭自有连接池;对于调用方拥有的连接池,只会解除监听器,除非显式选项转移了所有权。
  • 仅当 connectToShadowDb() 能创建隔离的影子数据库、连接到该数据库,并在释放/失败清理期间将其删除时,才实现 SqlMigrationAwareDriverAdapterFactory。
  • 绝不要将影子适配器指向主数据库。对生成的标识符进行引用,并使用经过加密学安全处理的唯一名称。
  • getConnectionInfo() 应准确报告 schemaName、适用时的 maxBindValues,以及 supportsRelationJoins。

验证清单

  • 根据确切的目标 @prisma/driver-adapter-utils 版本进行类型检查
  • queryRaw 保留列顺序、类型、空值和精度
  • executeRaw 正确报告受影响的行数
  • executeScript 处理提供程序特定的多语句语法
  • 并发交互式事务使用不同的专用连接
  • 成功时提交并仅释放一次;失败时回滚并仅释放一次
  • 嵌套事务测试覆盖创建/回滚/释放保存点钩子
  • 不支持的隔离级别会以 InvalidIsolationLevel 失败
  • 已知约束映射为结构化错误
  • 未映射的数据库错误保留原始代码/消息,并提供有用的 P2039
  • 测试内部和外部连接池的释放所有权行为
  • 影子数据库的创建、使用、失败清理和释放均相互隔离
  • 运行 Prisma Client 集成/端到端测试,而不只是适配器单元测试

源码参考

评论

支持 Markdown