关于 prisma-driver-adapter-implementation
面向按已安装的 `@prisma/driver-adapter-utils` 精确版本实现 Prisma SQL 驱动适配器的开发者。重点涵盖事务生命周期、值与错误映射、资源所有权及影子数据库隔离。
主要功能
- 01适配器工厂与连接契约
- 02查询值及元数据映射
- 03事务与保存点生命周期
- 04数据库错误映射
- 05连接池所有权与影子数据库规则
- 06实现验证清单
适用场景
- 01实现 Prisma SQL 驱动适配器
- 02添加嵌套事务支持
- 03排查适配器错误或连接泄漏
- 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 集成/端到端测试,而不只是适配器单元测试