跳到主要内容

TypedSQL

TypedSQL 入门

要在您的 Prisma 项目中开始使用 TypedSQL,请按照以下步骤操作

  1. 确保您已安装 @prisma/clientprisma,并更新至至少 5.19.0 版本。

    npm install @prisma/client@latest
    npm install -D prisma@latest
  2. typedSql 预览特性标志添加到您的 schema.prisma 文件中

    generator client {
    provider = "prisma-client-js"
    previewFeatures = ["typedSql"]
    }
  3. 在您的 prisma 目录中创建一个 sql 目录。您将在其中编写 SQL 查询。

    mkdir -p prisma/sql
  4. 在您的 prisma/sql 目录中创建一个新的 .sql 文件。例如,getUsersWithPosts.sql。请注意,文件名必须是有效的 JS 标识符,并且不能以 $ 开头。

  5. 在您的新 .sql 文件中编写 SQL 查询。例如

    prisma/sql/getUsersWithPosts.sql
    SELECT u.id, u.name, COUNT(p.id) as "postCount"
    FROM "User" u
    LEFT JOIN "Post" p ON u.id = p."authorId"
    GROUP BY u.id, u.name
  6. 使用 sql 标志生成 Prisma Client,以确保为您的 SQL 查询创建 TypeScript 函数和类型

    警告

    确保在生成带有 sql 标志的客户端之前,应用任何待处理的迁移。

    prisma generate --sql

    如果您不想在每次更改后都重新生成客户端,此命令也适用于现有的 --watch 标志

    prisma generate --sql --watch
  7. 现在您可以导入并在您的 TypeScript 代码中使用您的 SQL 查询

    /src/index.ts
    import { PrismaClient } from '@prisma/client'
    import { getUsersWithPosts } from '@prisma/client/sql'

    const prisma = new PrismaClient()

    const usersWithPostCounts = await prisma.$queryRawTyped(getUsersWithPosts())
    console.log(usersWithPostCounts)

向 TypedSQL 查询传递参数

要向您的 TypedSQL 查询传递参数,您可以使用参数化查询。这使您能够编写灵活且可重用的 SQL 语句,同时保持类型安全。以下是如何操作:

  1. 在您的 SQL 文件中,为您要传递的参数使用占位符。占位符的语法取决于您的数据库引擎

    对于 PostgreSQL,使用位置占位符 $1$2 等。

    prisma/sql/getUsersByAge.sql
    SELECT id, name, age
    FROM users
    WHERE age > $1 AND age < $2
    注意

    有关如何在 SQL 文件中定义参数类型 的信息,请参见下文。

  2. 在您的 TypeScript 代码中使用生成的函数时,将参数作为附加参数传递给 $queryRawTyped

    /src/index.ts
    import { PrismaClient } from '@prisma/client'
    import { getUsersByAge } from '@prisma/client/sql'

    const prisma = new PrismaClient()

    const minAge = 18
    const maxAge = 30
    const users = await prisma.$queryRawTyped(getUsersByAge(minAge, maxAge))
    console.log(users)

通过使用参数化查询,您可以确保类型安全并防止 SQL 注入漏洞。TypedSQL 生成器将根据您的 SQL 查询为参数创建适当的 TypeScript 类型,为查询结果和输入参数提供完整的类型检查。

向 TypedSQL 传递数组参数

TypedSQL 支持将数组作为 PostgreSQL 的参数传递。将 PostgreSQL 的 ANY 运算符与数组参数一起使用。

prisma/sql/getUsersByIds.sql
SELECT id, name, email
FROM users
WHERE id = ANY($1)
/src/index.ts
import { PrismaClient } from '@prisma/client'
import { getUsersByIds } from '@prisma/client/sql'

const prisma = new PrismaClient()

const userIds = [1, 2, 3]
const users = await prisma.$queryRawTyped(getUsersByIds(userIds))
console.log(users)

TypedSQL 将为数组参数生成适当的 TypeScript 类型,确保输入和查询结果的类型安全。

注意

在传递数组参数时,请注意您的数据库在单个查询中支持的最大占位符数量。对于非常大的数组,您可能需要将查询拆分为多个较小的查询。

在 SQL 文件中定义参数类型

TypedSQL 中的参数类型化是通过 SQL 文件中的特定注释完成的。这些注释的形式为

-- @param {Type} $N:alias optional description

其中 Type 是有效的数据库类型,N 是查询中参数的位置,alias 是用于 TypeScript 类型中的参数的可选别名。

例如,如果您需要键入一个别名为 name 且描述为 "The name of the user" 的单个字符串参数,则需要在 SQL 文件中添加以下注释

-- @param {String} $1:name The name of the user

要指示参数可为空,请在别名后添加问号

-- @param {String} $1:name? The name of the user (optional)

当前接受的类型为 IntBigIntFloatBooleanStringDateTimeJsonBytesnullDecimal

采用 上面的示例,SQL 文件将如下所示

-- @param {Int} $1:minAge
-- @param {Int} $2:maxAge
SELECT id, name, age
FROM users
WHERE age > $1 AND age < $2

参数类型定义的格式与数据库引擎无关。

注意

手动参数类型定义不支持数组参数。对于这些参数,您将需要依赖 TypedSQL 提供的类型推断。

示例

有关如何在各种场景中使用 TypedSQL 的实际示例,请参阅 Prisma 示例仓库。此仓库包含一系列即用型 Prisma 示例项目,这些项目演示了最佳实践和常见用例,包括 TypedSQL 实现。

TypedSQL 的局限性

支持的数据库

TypedSQL 支持现代版本的 MySQL 和 PostgreSQL,无需任何进一步的配置。对于低于 8.0 的 MySQL 版本和所有 SQLite 版本,您需要手动在 SQL 文件中 描述参数类型。在所有支持的 PostgreSQL 和 MySQL 8.0 及更高版本中,输入类型都是推断出来的。

TypedSQL 不适用于 MongoDB,因为它专为 SQL 数据库设计。

需要活动数据库连接

TypedSQL 需要活动数据库连接才能正常工作。这意味着您需要有一个正在运行的数据库实例,Prisma 可以在使用 --sql 标志生成客户端时连接到该实例。如果您的 Prisma 配置中提供了 directUrl,TypedSQL 将使用该 URL 进行连接。

带有动态列的动态 SQL 查询

TypedSQL 本身不支持构建带有动态添加列的 SQL 查询。当您需要创建列在运行时确定的查询时,必须使用 $queryRaw$executeRaw 方法。这些方法允许执行原始 SQL,其中可以包括动态列选择。

使用动态列选择的查询示例

const columns = 'name, email, age'; // Columns determined at runtime
const result = await prisma.$queryRawUnsafe(
`SELECT ${columns} FROM Users WHERE active = true`
);

在此示例中,要选择的列是动态定义的,并包含在 SQL 查询中。虽然这种方法提供了灵活性,但它需要仔细关注安全性,尤其要 避免 SQL 注入漏洞。此外,使用原始 SQL 查询意味着放弃 TypedSQL 的类型安全性和 DX。

致谢

此功能深受 PgTypedSQLx 的启发。此外,SQLite 解析由 SQLx 处理。