跳到主要內容

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

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

手動向 Prisma 模型新增 `@default` 屬性

你可以向 Prisma 模型新增 `@default` 屬性

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
}

變通方法

手動向 Prisma 模型新增 `@updatedAt` 屬性

作為變通方法,你可以手動將 `@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 對多(缺少 `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* 及更高版本中的內省結果

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 對多。

變通方法

手動為外部索引鍵列新增 `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])
}

所有非內聯關係都被識別為多對多

問題

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

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* 及更高版本中相同的關係表約定,因此該關係現在被識別為多對多關係。

變通方法

作為變通方法,你可以將資料遷移到與 Prisma ORM 的 1 對多關係相容的結構中

  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 對多。

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

列舉在資料庫中表示為 `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. 在資料庫中建立一個 `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";

內省後,該型別現在被正確識別為列舉。

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);

**注意**:使用升級 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 }
]
}

要僅訪問列表中的布林值,你可以按如下方式 `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 陣列中。以下是你可以這樣做的方法

  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.