跳到主要內容

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

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 的先前版本會被覆蓋)!

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

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))。然而,當你在 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)

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 資料,你現在必須始終在查詢中包含

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 ]
}
© . This site is unofficial and not affiliated with Prisma Data, Inc.