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相對應的enumCREATE TYPE "Role" AS ENUM ('CUSTOMER', 'ADMIN'); - 將型別從
TEXT更改為你的新enumALTER 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 ]
}