跳至主要内容

架构不兼容性

概述

本页的每个部分都描述了从 Prisma 1 升级到 Prisma ORM 2.x 及更高版本时可能遇到的问题,并解释了可用的解决方法。

默认值未在数据库中表示

问题

在 Prisma 1 数据模型中添加 @default 指令时,此字段的默认值是在运行时由 Prisma 1 服务器生成的。数据库列中没有添加 DEFAULT 约束。由于此约束未反映在数据库本身中,因此 Prisma ORM 2.x 及更高版本的内省无法识别它。

示例

Prisma 1 数据模型

type Post {
id: ID! @id
published: Boolean @default(value: false)
}

Prisma 1 生成的 SQL 迁移

CREATE TABLE "Post" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
published BOOLEAN NOT NULL
);

Prisma ORM 2.x 及更高版本中的内省结果

schema.prisma
model Post {
id String @id
published Boolean
}

由于在使用 prisma deploy 将 Prisma 1 数据模型映射到数据库时,DEFAULT 约束未添加到数据库,因此 Prisma ORM v2(以及更高版本)在内省期间无法识别它。

解决方法

手动将 DEFAULT 约束添加到数据库列

你可以通过以下方式修改列以添加 DEFAULT 约束

ALTER TABLE `Post`
ALTER COLUMN published SET DEFAULT false;

进行此调整后,你可以重新内省你的数据库,@default 属性将添加到 published 字段。

schema.prisma
model Post {
id String @id
published Boolean @default(false)
}

手动将 @default 属性添加到 Prisma 模型

你可以将 @default 属性添加到 Prisma 模型

schema.prisma
model Post {
id String
published Boolean @default(false)
}

如果在 Prisma 架构中设置了 @default 属性,并且你运行 prisma generate,则生成的 Prisma Client 代码将在运行时生成指定的默认值(类似于 Prisma 1 服务器在 Prisma 1 中所做的那样)。

生成的 CUID 作为 ID 值未在数据库中表示

问题

ID 字段用 @id 指令注释时,Prisma 1 会自动将 ID 值生成为 CUID。这些 CUID 是在运行时由 Prisma 1 服务器生成的。由于此行为未反映在数据库本身中,因此 Prisma ORM 2.x 及更高版本的内省无法识别它。

示例

Prisma 1 数据模型

type Post {
id: ID! @id
}

Prisma 1 生成的 SQL 迁移

CREATE TABLE "Post" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

Prisma ORM 2.x 及更高版本中的内省结果

schema.prisma
model Post {
id String @id
}

由于数据库中没有 CUID 行为的指示,因此 Prisma ORM 的内省无法识别它。

解决方法

作为解决方法,你可以手动将 @default(cuid()) 属性添加到 Prisma 模型

schema.prisma
model Post {
id String @id @default(cuid())
}

如果在 Prisma 架构中设置了 @default 属性,并且你运行 prisma generate,则生成的 Prisma Client 代码将在运行时生成指定的默认值(类似于 Prisma 1 服务器在 Prisma 1 中所做的那样)。

请注意,你必须在每次内省后重新添加此属性,因为内省会删除它(因为以前版本的 Prisma 架构会被覆盖)!

@createdAt 未在数据库中表示

问题

DateTime 字段用 @createdAt 指令注释时,Prisma 1 会自动生成值。这些值是在运行时由 Prisma 1 服务器生成的。由于此行为未反映在数据库本身中,因此 Prisma ORM 2.x 及更高版本的内省无法识别它。

示例

Prisma 1 数据模型

type Post {
id: ID! @id
createdAt: DateTime! @createdAt
}

Prisma 1 生成的 SQL 迁移

CREATE TABLE "Post" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
"createdAt" TIMESTAMP NOT NULL
);

Prisma ORM 2.x 及更高版本中的内省结果

schema.prisma
model Post {
id String @id
createdAt DateTime
}

解决方法

手动将 DEFAULT CURRENT_TIMESTAMP 添加到数据库列

你可以通过以下方式修改列以添加 DEFAULT 约束

ALTER TABLE "Post"
ALTER COLUMN "createdAt" SET DEFAULT CURRENT_TIMESTAMP;

进行此调整后,你可以重新内省你的数据库,@default 属性将添加到 createdAt 字段。

schema.prisma
model Post {
id String
createdAt DateTime @default(now())
}

手动将 @default(now()) 属性添加到 Prisma 模型

作为解决方法,你可以手动将 @default(now()) 属性添加到 Prisma 模型

schema.prisma
model Post {
id String @id
createdAt DateTime @default(now())
}

如果在 Prisma 架构中设置了 @default 属性,并且你运行 prisma generate,则生成的 Prisma Client 代码将在运行时生成指定的默认值(类似于 Prisma 1 服务器在 Prisma 1 中所做的那样)。

请注意,你必须在每次内省后重新添加此属性,因为内省会删除它(因为以前版本的 Prisma 架构会被覆盖)!

@updatedAt 未在数据库中表示

问题

DateTime 字段用 @updatedAt 指令注释时,Prisma 1 会自动生成值。这些值是在运行时由 Prisma 1 服务器生成的。由于此行为未反映在数据库本身中,因此 Prisma ORM 2.x 及更高版本的内省无法识别它。

示例

Prisma 1 数据模型

type Post {
id: ID! @id
updatedAt: DateTime! @updatedAt
}

Prisma 1 生成的 SQL 迁移

CREATE TABLE "Post" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
updatedAt TIMESTAMP
);

Prisma ORM 2.x 及更高版本中的内省结果

schema.prisma
model Post {
id String @id
updatedAt DateTime
}

解决方法

手动将 @updatedAt 属性添加到 Prisma 模型

作为解决方法,你可以手动将 @updatedAt 属性添加到 Prisma 模型

schema.prisma
model Post {
id String @id
updatedAt DateTime @updatedAt
}

如果在 Prisma 架构中设置了 @updatedAt 属性,并且你运行 prisma generate,则生成的 Prisma Client 代码将在更新现有记录时自动为该列生成值(类似于 Prisma 1 服务器在 Prisma 1 中所做的那样)。

请注意,你必须在每次内省后重新添加此属性,因为内省会删除它(因为以前版本的 Prisma 架构会被覆盖)!

内联 1-1 关系被识别为 1-n(缺少 UNIQUE 约束)

问题

在 Prisma ORM v1.31 中引入的 数据模型 v1.1 中,1-1 关系可以声明为内联。在这种情况下,关系不会通过 关系表 维持,而是通过两个参与表之一的单个外键维持。

当使用此方法时,Prisma ORM 不会向外键列添加 UNIQUE 约束,这意味着在 Prisma ORM 版本 2.x 及更高版本中的内省之后,此以前的 1-1 关系将作为 1-n 关系添加到 Prisma 架构。

示例

Prisma ORM 数据模型 v1.1(从 Prisma ORM v1.31 开始可用)

type User {
id: ID! @id
profile: Profile @relation(link: INLINE)
}

type Profile {
id: ID! @id
user: User
}

请注意,在这种情况下省略 `@relation` 指令将导致相同的结果,因为 `link: INLINE` 是 1-1 关系的**默认值**。

Prisma 1 生成的 SQL 迁移

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

CREATE TABLE "Profile" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
"user" VARCHAR(25),
FOREIGN KEY ("user") REFERENCES "User"(id)
);

在 Prisma ORM 2.x 及更高版本中进行内省的结果

schema.prisma
model User {
id String @id
Profile Profile[]
}

model Profile {
id String @id
user String?
User User? @relation(fields: [user], references: [id])
}

由于 `user` 列(表示该关系中的外键)没有定义 `UNIQUE` 约束,因此 Prisma ORM 的内省会将该关系识别为 1-n。

解决方法

手动将 `UNIQUE` 约束添加到外键列

您可以修改外键列以添加 `UNIQUE` 约束,如下所示

ALTER TABLE `Profile`
ADD CONSTRAINT userId_unique UNIQUE (`user`);

完成此调整后,您可以重新内省您的数据库,1-1 关系将被正确识别。

schema.prisma
model User {
id String @id
Profile Profile?
}

model Profile {
id String @id
user String? @unique
User User? @relation(fields: [user], references: [id])
}

**所有** 非内联关系都被识别为 m-n

问题

Prisma 1 大多数情况下将关系表示为关系表。

  • Prisma 1 **数据模型 v1.0** 中的所有关系都被表示为关系表。
  • 在 **数据模型 v1.1** 中,所有 m-n 关系以及声明为 `link: TABLE` 的 1-1 和 1-n 关系都被表示为关系表。

由于这种表示方式,在 Prisma ORM 2.x 及更高版本中进行内省时,所有这些关系都将被识别为 m-n 关系,即使它们可能在 Prisma 1 中被声明为 1-1 或 1-n。

示例

Prisma 1 数据模型

type User {
id: ID! @id
posts: [Post!]!
}

type Post {
id: ID! @id
author: User! @relation(link: TABLE)
}

Prisma 1 生成的 SQL 迁移

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

CREATE TABLE "Post" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

CREATE TABLE "_PostToUser" (
"A" VARCHAR(25) NOT NULL REFERENCES "Post"(id) ON DELETE CASCADE,
"B" VARCHAR(25) NOT NULL REFERENCES "User"(id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX "_PostToUser_AB_unique" ON "_PostToUser"("A" text_ops,"B" text_ops);
CREATE INDEX "_PostToUser_B" ON "_PostToUser"("B" text_ops);

在 Prisma ORM 2.x 及更高版本中进行内省的结果

schema.prisma
model User {
id String @id
Post Post[] @relation(references: [id])
}

model Post {
id String @id
User User[] @relation(references: [id])
}

由于 Prisma 1 创建的关系表使用了与 Prisma ORM 2.x 及更高版本相同的关系表约定,该关系现在被识别为 m-n 关系。

解决方法

作为解决方法,您可以将数据迁移到与 Prisma ORM 的 1-n 关系兼容的结构中。

  1. 在 `Post` 表中创建一个新的列 `authorId`。此列应为一个外键,引用 `User` 表的 `id` 字段。
    ALTER TABLE `Post` ADD COLUMN `authorId` VARCHAR(25);
    ALTER TABLE `Post` ADD FOREIGN KEY (`authorId`) REFERENCES `User` (`id`);
  2. 编写一个 SQL 查询,读取 `_PostToUser` 关系表中的所有行,并针对每一行
    1. 通过查找 `A` 列的值来查找相应的 `Post` 记录。
    2. 将 `B` 列的值作为 `authorId` 的值插入到该 `Post` 记录中。
    UPDATE Post, _PostToUser
    SET Post.authorId = _PostToUser.B
    WHERE Post.id = _PostToUser.A
  3. 删除 `_PostToUser` 关系表。
    DROP TABLE `_PostToUser`;

之后,您可以内省您的数据库,该关系将被识别为 1-n。

schema.prisma
model User {
id String @id
Post Post[]
}

model Post {
id String @id
User User @relation(fields: [authorId], references: [id])
authorId String
}

`Json` 类型在数据库中表示为 `TEXT`

问题

Prisma 1 在其数据模型中支持 `Json` 数据类型。但是,在底层数据库中,类型为 `Json` 的字段实际上是使用底层数据库的 `TEXT` 数据类型作为普通字符串存储的。任何对存储的 JSON 数据的解析和验证都在运行时由 Prisma 1 服务器完成。

示例

Prisma 1 数据模型

type User {
id: ID! @id
jsonData: Json
}

Prisma 1 生成的 SQL 迁移

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
jsonData TEXT
);

在 Prisma ORM 2.x 及更高版本中进行内省的结果

schema.prisma
model User {
id String @id
jsonData String?
}

解决方法

您可以手动将列的类型更改为 `JSON`。

ALTER TABLE User MODIFY COLUMN jsonData JSON;

完成此调整后,您可以重新内省您的数据库,该字段现在将被识别为 `Json`。

schema.prisma
model User {
id String @id
jsonData Json?
}

枚举在数据库中表示为 `TEXT`

问题

Prisma 1 在其数据模型中支持 `enum` 数据类型。但是,在底层数据库中,声明为 `enum` 的类型实际上是使用底层数据库的 `TEXT` 数据类型作为普通字符串存储的。任何对存储的 `enum` 数据的验证都在运行时由 Prisma 1 服务器完成。

示例

Prisma 1 数据模型

type User {
id: ID! @id
role: Role
}

enum Role {
ADMIN
CUSTOMER
}

Prisma 1 生成的 SQL 迁移

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
role TEXT
);

在 Prisma ORM 2.x 及更高版本中进行内省的结果

schema.prisma
model User {
id String @id
role String?
}

解决方法

您可以手动将 `role` 列转换为具有您所需值的枚举。

  1. 在您的数据库中创建一个与您在 Prisma 1 数据模型中定义的 `enum` 相匹配的 `enum`。
    CREATE TYPE "Role" AS ENUM ('CUSTOMER', 'ADMIN');
  2. 将类型从 `TEXT` 更改为您的新 `enum`。
    ALTER TABLE "User" ALTER COLUMN "role" TYPE "Role"
    USING "role"::text::"Role";

内省后,该类型现在被正确识别为枚举。

schema.prisma
model User {
id String @id
role Role?
}

enum Role {
ADMIN
CUSTOMER
}

不匹配的 CUID 长度

问题

Prisma 1 使用 CUID 作为所有数据库记录的 ID 值。在底层数据库中,这些 ID 表示为最大大小为 25 个字符的字符串(作为 `VARCHAR(25)`)。但是,在使用 `@default(cuid())` 在您的 Prisma ORM 2.x(或更高版本)模式中配置默认 CUID 时,生成的 ID 值可能会超过 25 个字符的限制(最大长度可能是 30 个字符)。因此,为了使您的 ID 能够用于 Prisma ORM 2.x(或更高版本),您需要将列类型调整为 `VARCHAR(30)`。

示例

Prisma 1 数据模型

type User {
id: ID! @id
}

Prisma 1 生成的 SQL 迁移

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

在 Prisma ORM 2.x 及更高版本中进行内省的结果

schema.prisma
model User {
id String @id
}

解决方法

您可以手动将 `VARCHAR(25)` 列转换为 `VARCHAR(30)`。

SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE `User` CHANGE `id` `id` char(30) CHARACTER SET utf8 NOT NULL;
SET FOREIGN_KEY_CHECKS=1;

**注意**:使用升级 CLI修复此问题时,即使您已在底层数据库中更改了列类型,生成的 SQL 语句也会继续出现在升级 CLI 中。这是升级 CLI 的当前限制。

标量列表(数组)使用额外的表维护

问题

在 Prisma 1 中,您可以在模型上定义标量类型的列表。在幕后,这是通过一个额外的表实现的,该表跟踪列表中的值。

为了删除使用额外表的做法(这会产生隐藏的性能成本),Prisma ORM 2.x 及更高版本只支持当您使用的数据库原生支持标量列表时才支持标量列表。目前,只有PostgreSQL 原生支持标量列表(数组).

对于 PostgreSQL,因此您可以在 Prisma ORM 2.x 及更高版本中继续使用标量列表,但您需要执行数据迁移,将数据从 Prisma 1 的额外表迁移到实际的 PostgreSQL 数组中。

示例

Prisma 1 数据模型

type User {
id: ID! @id
coinflips: [Boolean!]! @scalarList(strategy: RELATION)
}

Prisma 1 生成的 SQL 迁移

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

CREATE TABLE "User_coinflips" (
"nodeId" VARCHAR(25) REFERENCES "User"(id),
position INTEGER,
value BOOLEAN NOT NULL,
CONSTRAINT "User_coinflips_pkey" PRIMARY KEY ("nodeId", position)
);
CREATE UNIQUE INDEX "User_coinflips_pkey" ON "User_coinflips"("nodeId" text_ops,position int4_ops);

Prisma ORM 2 内省的结果

schema.prisma
model User {
id String @id
User_coinflips User_coinflips[]
}

model User_coinflips {
nodeId String
position Int
value Boolean
User User @relation(fields: [nodeId], references: [id])

@@id([nodeId, position])
}

请注意,您现在可以生成 Prisma Client,并且能够通过额外的表访问标量列表中的数据。PostgreSQL 用户可以选择将数据迁移到原生 PostgreSQL 数组中,并继续享受 Prisma Client API 为标量列表提供的流畅体验(有关更多信息,请阅读下面的部分)。

展开以查看 Prisma Client API 调用示例

要访问 coinflips 数据,您现在必须始终在查询中include它。

const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
coinflips: {
orderBy: { position: 'asc' },
},
},
})

注意orderBy 对于保留列表的顺序很重要。

这是 `查询结果

{
id: 1,
name: 'Alice',
coinflips: [
{ id: 1, position: 1000, value: false },
{ id: 2, position: 2000, value: true },
{ id: 3, position: 3000, value: false },
{ id: 4, position: 4000, value: true },
{ id: 5, position: 5000, value: true },
{ id: 6, position: 6000, value: false }
]
}

要访问列表中的布尔值,您可以像下面这样在 user 上的 coinflips 上执行 map 操作

const currentCoinflips = user!.coinflips.map((cf) => cf.value)

注意:上面的感叹号表示您正在强制解包 user 值。这是因为从先前查询返回的 user 可能为 null

以下是调用 mapcurrentCoinflips 的值

[false, true, false, true, true, false]

解决方法

以下解决方法仅适用于 PostgreSQL 用户!

由于标量列表(例如 数组)是 PostgreSQL 的原生功能,您可以继续在 Prisma 模式中使用相同的 coinflips: Boolean[] 表示法。

但是,要做到这一点,您需要手动将基础数据从 User_coinflips 表迁移到 PostgreSQL 数组。以下是如何操作:

  1. 将新的 coinflips 列添加到 User
    ALTER TABLE "User" ADD COLUMN coinflips BOOLEAN[];
  2. 将数据从 "User_coinflips".value 迁移到 "User.coinflips"
    UPDATE "User"
    SET coinflips = t.flips
    FROM (
    SELECT "nodeId", array_agg(VALUE ORDER BY position) AS flips
    FROM "User_coinflips"
    GROUP BY "nodeId"
    ) t
    where t."nodeId" = "User"."id";
  3. 要进行清理,您可以删除 User_coinflips
    DROP TABLE "User_coinflips";

您现在可以内省您的数据库,并且 coinflips 字段将在您的新 Prisma 模式中被表示为数组

schema.prisma
model User {
id String @id
coinflips Boolean[]
}

您可以像以前一样继续使用 Prisma Client

const user = await prisma.user.findUnique({
where: { id: 1 },
})

这是 API 调用的结果

{
id: 1,
name: 'Alice',
coinflips: [ false, true, false, true, true, false ]
}