跳到主要內容

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 及更高版本中內省的結果

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 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 及更高版本中內省的結果

schema.prisma
model Post {
id String @id
}

由於資料庫中沒有 CUID 行為的指示,Prisma ORM 的內省無法識別它。

解決方法

作為一種解決方法,您可以手動將 @default(cuid()) 屬性新增到 Prisma 模型

schema.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 及更高版本中內省的結果

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 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 及更高版本中內省的結果

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

解決方法

手動將 @updatedAt 屬性新增到 Prisma 模型

作為一種解決方法,您可以手動將 @updatedAt 屬性新增到 Prisma 模型

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

如果在 Prisma schema 中設定了 @updatedAt 屬性,並且您執行 prisma generate,則產生的 Prisma Client 程式碼將在更新現有記錄時自動產生此欄位的值(類似於 Prisma 1 伺服器在 Prisma 1 中的做法)。

請注意,您必須在每次內省後重新新增該屬性,因為內省會將其移除(因為先前版本的 Prisma schema 會被覆寫)!

Inline 1-1 關係被識別為 1-n(缺少 UNIQUE 約束)

問題

datamodel v1.1 中,在 Prisma ORM v1.31 中引入,1-1 關係可以宣告為inline。在這種情況下,關係將不會透過關係表維護,而是透過其中一個涉及的表上的單個外鍵維護。

當使用這種方法時,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 及更高版本中內省的結果

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])
}

所有非 inline 關係都被識別為 m-n

問題

Prisma 1 大多數情況下將關係表示為關係表

  • Prisma 1 datamodel v1.0 中的所有關係都表示為關係表
  • datamodel 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 關係中關係表的慣例,因此現在該關係被識別為 m-n 關係。

解決方法

作為一種解決方法,您可以將資料遷移到與 Prisma ORM 的 1-n 關係相容的結構中

  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");
  2. 編寫 SQL 查詢,讀取 _PostToUser 關係表中的所有列,並針對每一列
    1. 透過查詢欄位 A 中的值來尋找相應的 Post 記錄
    2. 將欄位 B 中的值作為 authorId 的值插入到該 Post 記錄中
    UPDATE "Post" post
    SET "authorId" = post_to_user."B"
    FROM "_PostToUser" post_to_user
    WHERE post_to_user."A" = post."id";
  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" ALTER COLUMN "jsonData" TYPE JSON  USING "jsonData"::json;

調整之後,您可以重新內省您的資料庫,並且該欄位現在將被識別為 Json

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

Enums 在資料庫中表示為 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 欄位變成具有所需值的 enum

  1. 在您的資料庫中建立一個 enum,以鏡像您在 Prisma 1 資料模型中定義的 enum
    CREATE TYPE "Role" AS ENUM ('CUSTOMER', 'ADMIN');
  2. 將類型從 TEXT 變更為新的 enum
    ALTER TABLE "User" ALTER COLUMN "role" TYPE "Role"
    USING "role"::text::"Role";

內省之後,該類型現在被正確識別為 enum

schema.prisma
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 及更高版本中內省的結果

schema.prisma
model User {
id String @id
}

解決方法

您可以手動將 VARCHAR(25) 欄位變成 VARCHAR(30)

ALTER TABLE "User" ALTER COLUMN "id" SET DATA TYPE character varying(30);

注意:當使用 Upgrade CLI 修正此問題時,即使您已變更底層資料庫中的欄位類型,產生的 SQL 語句仍會持續出現在 Upgrade CLI 中。這是目前 Upgrade 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 schema 中使用相同的 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 中表示為陣列

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 ]
}