MySQL 中的 Schema 不兼容性
概述
本页的每个部分都描述了从 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 及更高版本中的自省结果
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
字段中
model Post {
id String @id
published Boolean @default(false)
}
手动将 @default
属性添加到 Prisma 模型
你可以将 @default
属性添加到 Prisma 模型
model Post {
id String
published Boolean @default(false)
}
如果在 Prisma schema 中设置了 @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 及更高版本中的自省结果
model Post {
id String @id
}
由于数据库中没有 CUID 行为的指示,Prisma ORM 的自省功能无法识别它。
变通方法
作为一种变通方法,你可以手动将 @default(cuid())
属性添加到 Prisma 模型中
model Post {
id String @id @default(cuid())
}
如果在 Prisma schema 中设置了 @default
属性并运行 prisma generate
,生成的 Prisma Client 代码将在运行时生成指定的默认值(类似于 Prisma 1 服务器在 Prisma 1 中所做的工作)。
请注意,每次自省后你都必须重新添加该属性,因为自省会将其删除(因为 Prisma schema 的先前版本会被覆盖)!
@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 及更高版本中的自省结果
model Post {
id String @id
createdAt DateTime
}
变通方法
手动将 DEFAULT CURRENT_TIMESTAMP
添加到数据库列
你可以通过以下方式更改列以添加 DEFAULT
约束
ALTER TABLE "Post"
ALTER COLUMN "createdAt" SET DEFAULT CURRENT_TIMESTAMP;
进行此调整后,你可以重新自省数据库,@default
属性将添加到 createdAt
字段中
model Post {
id String
createdAt DateTime @default(now())
}
手动将 @default(now())
属性添加到 Prisma 模型
作为一种变通方法,你可以手动将 @default(now())
属性添加到 Prisma 模型中
model Post {
id String @id
createdAt DateTime @default(now())
}
如果在 Prisma schema 中设置了 @default
属性并运行 prisma generate
,生成的 Prisma Client 代码将在运行时生成指定的默认值(类似于 Prisma 1 服务器在 Prisma 1 中所做的工作)。
请注意,每次自省后你都必须重新添加该属性,因为自省会将其删除(因为 Prisma schema 的先前版本会被覆盖)!
@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 及更高版本中的自省结果
model Post {
id String @id
updatedAt DateTime
}
变通方法
手动将 @updatedAt
属性添加到 Prisma 模型
作为一种变通方法,你可以手动将 @updatedAt
属性添加到 Prisma 模型中
model Post {
id String @id
updatedAt DateTime @updatedAt
}
如果在 Prisma schema 中设置了 @updatedAt
属性并运行 prisma generate
,生成的 Prisma Client 代码将在更新现有记录时自动为此列生成值(类似于 Prisma 1 服务器在 Prisma 1 中所做的工作)。
请注意,每次自省后你都必须重新添加该属性,因为自省会将其删除(因为 Prisma schema 的先前版本会被覆盖)!
内联 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 schema 中。
示例
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 及更高版本中的自省结果
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 关系将得到正确识别
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 及更高版本中的自省结果
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 关系兼容的结构中
- 在
Post
表上创建新列authorId
。此列应为引用User
表的id
字段的外键ALTER TABLE `Post` ADD COLUMN `authorId` VARCHAR(25);
ALTER TABLE `Post` ADD FOREIGN KEY (`authorId`) REFERENCES `User` (`id`); - 编写一个 SQL 查询,读取
_PostToUser
关系表中的所有行,并对每一行执行以下操作- 通过查找列
A
中的值来找到相应的Post
记录 - 将列
B
中的值作为authorId
的值插入到该Post
记录中
UPDATE Post, _PostToUser
SET Post.authorId = _PostToUser.B
WHERE Post.id = _PostToUser.A - 通过查找列
- 删除
_PostToUser
关系表DROP TABLE `_PostToUser`;
之后你可以自省数据库,该关系现在将被识别为 1-n
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 及更高版本中的自省结果
model User {
id String @id
jsonData String?
}
变通方法
你可以手动将列的类型更改为 JSON
ALTER TABLE User MODIFY COLUMN jsonData JSON;
进行此调整后,你可以重新自省数据库,该字段现在将被识别为 Json
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 及更高版本中的自省结果
model User {
id String @id
role String?
}
变通方法
你可以手动将 role
列转换为包含所需值的枚举
- 在数据库中创建一个与你在 Prisma 1 数据模型中定义的
enum
相对应的enum
CREATE TYPE "Role" AS ENUM ('CUSTOMER', 'ADMIN');
- 将类型从
TEXT
更改为你的新enum
ALTER TABLE "User" ALTER COLUMN "role" TYPE "Role"
USING "role"::text::"Role";
自省后,该类型现在被正确识别为枚举
model User {
id String @id
role Role?
}
enum Role {
ADMIN
CUSTOMER
}
CUID 长度不匹配
问题
Prisma 1 使用 CUID 作为所有数据库记录的 ID 值。在底层数据库中,这些 ID 表示为最大长度为 25 个字符的字符串(如 VARCHAR(25)
)。然而,当你在 Prisma ORM 2.x(或更高版本)schema 中使用 @default(cuid())
配置默认 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 及更高版本中的自省结果
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 自省结果
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 数据,你现在必须始终在查询中包含
它
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
。
以下是调用 map
后 currentCoinflips
的值
[false, true, false, true, true, false]
变通方法
以下变通方法仅适用于 PostgreSQL 用户!
由于标量列表(即数组)作为 PostgreSQL 的原生功能可用,你可以在 Prisma schema 中继续使用相同的 coinflips: Boolean[]
符号。
然而,为了实现这一点,你需要手动将底层数据从 User_coinflips
表迁移到 PostgreSQL 数组中。你可以这样做:
- 将新的
coinflips
列添加到User
表ALTER TABLE "User" ADD COLUMN coinflips BOOLEAN[];
- 将数据从
"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"; - 为了清理,你可以删除
User_coinflips
表DROP TABLE "User_coinflips";
你现在可以自省数据库,coinflips
字段将在你的新 Prisma schema 中表示为数组
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 ]
}