PostgreSQL 中的模式不相容性
概述
本頁面的每個部分都描述了從 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)
}
手動向 Prisma 模型新增 `@default` 屬性
你可以向 Prisma 模型新增 `@default` 屬性
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
}
變通方法
手動向 Prisma 模型新增 `@updatedAt` 屬性
作為變通方法,你可以手動將 `@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 對多(缺少 `UNIQUE` 約束)
問題
在 Prisma ORM v1.31 中引入的 資料模型 v1.1 中,1 對 1 關係可以宣告為內聯。在這種情況下,關係不會透過關係表維護,而是透過所涉及的兩個表之一上的單個外部索引鍵維護。
當使用此方法時,Prisma ORM 不會向外鍵列新增 `UNIQUE` 約束,這意味著在 Prisma ORM 2.*x* 及更高版本中進行內省後,這種以前的 1 對 1 關係將作為 1 對多關係新增到 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 對多。
變通方法
手動為外部索引鍵列新增 `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])
}
所有非內聯關係都被識別為多對多
問題
Prisma 1 大部分時間將關係表示為關係表
- Prisma 1 資料模型 v1.0 中的所有關係都表示為關係表
- 在 資料模型 v1.1 中,所有多對多關係以及宣告為 `link: TABLE` 的 1 對 1 和 1 對多關係都表示為關係表。
由於這種表示方式,Prisma ORM 2.*x* 及更高版本中的內省會將所有這些關係識別為多對多關係,即使它們在 Prisma 1 中可能被宣告為 1 對 1 或 1 對多。
示例
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* 及更高版本中相同的關係表約定,因此該關係現在被識別為多對多關係。
變通方法
作為變通方法,你可以將資料遷移到與 Prisma ORM 的 1 對多關係相容的結構中
- 在 `Post` 表上建立新列 `authorId`。此列應為引用 `User` 表 `id` 欄位的外部索引鍵。
ALTER TABLE "Post" ADD COLUMN "authorId" VARCHAR(25);
ALTER TABLE "Post"
ADD CONSTRAINT fk_author
FOREIGN KEY ("authorId")
REFERENCES "User"("id"); - 編寫一個 SQL 查詢,讀取 `_PostToUser` 關係表中的所有行,並對每行執行以下操作
- 透過查詢列 `A` 中的值來找到相應的 `Post` 記錄
- 將列 `B` 中的值作為 `authorId` 的值插入到該 `Post` 記錄中
UPDATE "Post" post
SET "authorId" = post_to_user."B"
FROM "_PostToUser" post_to_user
WHERE post_to_user."A" = post."id"; - 刪除 `_PostToUser` 關係表
DROP TABLE "_PostToUser";
之後,你可以內省你的資料庫,該關係現在將被識別為 1 對多。
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" ALTER COLUMN "jsonData" TYPE JSON USING "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` 列轉換為具有你所需值的列舉
- 在資料庫中建立一個 `enum`,以映象你在 Prisma 1 資料模型中定義的 `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)`
ALTER TABLE "User" ALTER COLUMN "id" SET DATA TYPE character varying(30);
**注意**:使用升級 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 }
]
}
要僅訪問列表中的布林值,你可以按如下方式 `map` `user` 上的 `coinflips`
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 ]
}