随着今天发布的 v5.19.0 版本,Prisma ORM 引入了一种新的方式来以类型安全的方式编写原生 SQL 查询!现在您可以同时享受到 Prisma ORM 的两大优势:适用于大多数查询的便捷高级抽象,以及适用于原生 SQL 的灵活且类型安全的逃逸舱。
快速概述:我们使原生 SQL 完全类型安全
使用 Prisma ORM,我们设计了我们认为最佳的 API 来编写常规 CRUD 查询,这些查询构成了大多数应用程序的 95%!
对于剩下的 5% —— 那些无法用 Prisma Client API 表达或需要最大性能的复杂查询 —— 我们提供了一个更低级别的 API 来编写原生 SQL。然而,这个逃逸舱没有提供类型安全性,开发者错过了他们习惯于从 Prisma ORM 获得的卓越开发体验,所以我们寻找了更好的方法!
随着今天 Prisma ORM v5.19.0 版本的发布,我们很高兴地宣布 TypedSQL:编写复杂和高性能查询的最佳方式。TypedSQL 是更好的 SQL。它是完全类型安全的,提供自动补全,并且在您需要编写原生 SQL 查询时提供出色的开发体验。以下是它的工作原理:
在
.sql
文件中编写 SQL 查询并将其放入prisma/sql
目录:-- prisma/sql/conversionByVariant.sql SELECT "variant", CAST("checked_out" AS FLOAT) / CAST("opened" AS FLOAT) AS "conversion" FROM ( SELECT "variant", COUNT(*) FILTER (WHERE "type"='PageOpened') AS "opened", COUNT(*) FILTER (WHERE "type"='CheckedOut') AS "checked_out" FROM "TrackingEvent" GROUP BY "variant" ) AS "counts" ORDER BY "conversion" DESC
您还可以创建带有参数的 SQL 查询!
-- prisma/sql/conversionByVariantByVersion.sql -- 注意这个语法是针对 PostgreSQL 的。 -- 参数语法将取决于您的数据库引擎。 SELECT "variant", CAST("checked_out" AS FLOAT) / CAST("opened" AS FLOAT) AS "conversion" FROM ( SELECT "variant", COUNT(*) FILTER (WHERE "type"='PageOpened') AS "opened", COUNT(*) FILTER (WHERE "type"='CheckedOut') AS "checked_out" FROM "TrackingEvent" WHERE version = $1 GROUP BY "variant" ) AS "counts" ORDER BY "conversion" DESC
使用
prisma generate
上的--sql
标志生成查询函数:npx prisma generate --sql
从
@prisma/client/sql
导入查询函数 …import { PrismaClient } from '@prisma/client' import { conversionByVariant } from '@prisma/client/sql'
… 然后在新的
$queryRawTyped
函数中调用它以获得完全类型化的结果 😎// `result` 是完全类型化的! const result = await prisma.$queryRawTyped(conversionByVariant())
如果您的 SQL 查询有参数,它们会提供给传递给
$queryRawTyped
的查询函数// 只给我从版本 5 的 TrackingEvent 中获取转换结果 const result = await prisma.$queryRawTyped(conversionByVariantByVersion(5))
Prisma Client API 与 TypedSQL 的结合为 CRUD 操作和高度复杂的查询提供了最佳体验。有了这个新增功能,我们希望您再也不需要触摸 SQL 查询构建器了!
高级抽象带来高生产力
原生 SQL 仍然是查询关系数据库中最强大和灵活的方式。但它确实有一些缺点。
原生 SQL 的缺点
如果您之前在 TypeScript 项目中编写过原生 SQL,您可能知道它并不提供最佳的开发体验:
- 编写 SQL 查询时没有自动补全。
- 查询结果没有类型安全性。
- 编写和调试复杂 SQL 查询的复杂性。
- 开发团队通常具有不同水平的 SQL 经验,不是每个团队成员都擅长编写 SQL。
- SQL 使用不同的数据模型(关系)与 TypeScript(对象)相比,需要从一种映射到另一种;这在处理模型之间的关系时尤为明显,这些关系在 SQL 中通过 外键 表示,但在 TypeScript 中作为 嵌套对象 表示。
应用开发者应该关心 数据 —— 而不是 SQL
在 Prisma,我们坚信应用开发者应该关心 数据 —— 而不是 SQL。
典型应用开发者编写的大多数查询使用的功能相当有限,通常与常见的 CRUD 操作有关,如 分页、 过滤器 或 嵌套查询。
我们的主要目标是确保应用开发者能够快速获得他们需要的数据,而不需要过多考虑查询和将数据库中的行映射到他们代码中的对象。
使用 Prisma ORM 快速发货
这就是我们构建 Prisma ORM 的原因,为开发者提供一种抽象,使他们高效并快速发货!以下是使用 Prisma ORM 的典型工作流程概述。
首先,您在一个人类可读的模式中定义您的数据模型:
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
然后,您可以使用 Prisma CLI 生成(可定制的)SQL 迁移,并针对数据库运行迁移。一旦模式映射到您的数据库,您可以使用 Prisma Client 进行查询:
// 创建带有帖子的新用户
await prisma.user.create({
data: {
name: 'Alice',
email: 'alice@prisma.io',
posts: {
create: { title: 'Hello World' },
},
},
})
逃逸舱:下降到原生 SQL
虽然我们相信这种更高级的抽象使开发者更有生产力,但我们已经看到许多项目需要编写原生 SQL 的选项。这通常发生在:
- Prisma Client API 无法灵活地表达某个查询。
- 查询需要为速度进行优化。
在这些情况下,Prisma ORM 通过使用 Prisma Client 的 $queryRaw
方法提供了一个原生 SQL 的逃逸舱:
const result = await prisma.$queryRaw`
SELECT "variant", CAST("checked_out" AS FLOAT) / CAST("opened" AS FLOAT) AS "conversion"
FROM (
SELECT
"variant",
COUNT(*) FILTER (WHERE "type"='PageOpened') AS "opened",
COUNT(*) FILTER (WHERE "type"='CheckedOut') AS "checked_out"
FROM "TrackingEvent"
GROUP BY "variant"
) AS "counts"
ORDER BY "conversion" DESC
`
这种方法的主要问题是这个查询不是类型安全的。如果开发者想要享受标准 Prisma Client API 提供的类型安全性好处,他们需要手动编写这个查询的返回类型,这可能既繁琐又耗时。另一个问题是这些手动定义的类型不会随着模式变化自动更新,这引入了另一个错误的可能性。
虽然有方法可以通过 Prisma ORM 的原生查询来提高开发体验,例如使用 Kysely 查询构建器扩展 for Prisma Cient 或 SafeQL,但我们希望以原生方式解决这个问题。
Prisma ORM 新增功能:TypedSQL 🎉
这就是为什么我们很高兴地介绍 TypedSQL,Prisma ORM 中的一个新工作流程,为您提供原生 SQL 查询的类型安全性。TypedSQL 的灵感来自像 PgTyped 和 sqlx 这样的项目,它们基于类似的想法。
有了 TypedSQL,Prisma ORM 现在为您提供了最好的两全其美:
- 一个使开发者高效且能够服务于项目中大多数查询的高级抽象。
- 当您需要直接编写 SQL 时,一个令人愉快且类型安全的逃逸舱。
它还为开发团队提供了选择他们最喜欢的方法的选项:您的团队中是否有工程师是铁杆 SQL 粉丝,但也有些人不愿意接触 SQL?
Prisma ORM 现在为两组人都提供了他们想要的东西,而不会牺牲开发体验或灵活性!