跳到主要內容

Schema incompatibilities

Overview

本頁面的每個章節都描述了從 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 結構描述中設定了 @default 屬性,並且您執行 prisma generate,則產生的 Prisma Client 程式碼將在執行時產生指定的預設值(類似於 Prisma 1 伺服器在 Prisma 1 中所做的那樣)。

產生的 CUID 作為 ID 值未在資料庫中表示

問題

Prisma 1 會在以 @id 指令註釋時,為 ID 欄位自動產生 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 結構描述中設定了 @default 屬性,並且您執行 prisma generate,則產生的 Prisma Client 程式碼將在執行時產生指定的預設值(類似於 Prisma 1 伺服器在 Prisma 1 中所做的那樣)。

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

@createdAt 未在資料庫中表示

問題

Prisma 1 會在以 @createdAt 指令註釋時,為 DateTime 欄位自動產生值。這些值是由 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 結構描述中設定了 @default 屬性,並且您執行 prisma generate,則產生的 Prisma Client 程式碼將在執行時產生指定的預設值(類似於 Prisma 1 伺服器在 Prisma 1 中所做的那樣)。

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

@updatedAt 未在資料庫中表示

問題

Prisma 1 會在以 @updatedAt 指令註釋時,為 DateTime 欄位自動產生值。這些值是由 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 結構描述中設定了 @updatedAt 屬性,並且您執行 prisma generate,則產生的 Prisma Client 程式碼將在現有記錄更新時自動為此資料行產生值(類似於 Prisma 1 伺服器在 Prisma 1 中所做的那樣)。

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

內嵌 1-1 關聯被識別為 1-n(缺少 UNIQUE 約束)

問題

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

當使用這種方法時,Prisma ORM 不會將 UNIQUE 約束新增到外來鍵資料行,這表示在 Prisma ORM 2.x 及更高版本中進行內省後,此先前的 1-1 關聯將作為 1-n 關聯新增到 Prisma 結構描述。

範例

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 1 中可能已宣告為 1-1 或 1-n,Prisma ORM 2.x 及更高版本中的內省功能也會將所有這些關聯識別為 m-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 datamodel

type User {
id: ID! @id
jsonData: Json
}

Prisma 1 generated SQL migration

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
jsonData TEXT
);

Result of introspection in Prisma ORM 2.x and later versions

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

Workaround

You can manually change the type of the column to JSON

ALTER TABLE User MODIFY COLUMN jsonData JSON;

After this adjustment, you can re-introspect your database and the field will now be recognized as Json

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

Enums are represented as TEXT in database

Problem

Prisma 1 supports the enum data type in its datamodel. However, in the underlying database, types declared as enum are actually stored as plain strings using the TEXT data type of the underlying database. Any validation of the stored enum data is done by the Prisma 1 server at runtime.

Example

Prisma 1 datamodel

type User {
id: ID! @id
role: Role
}

enum Role {
ADMIN
CUSTOMER
}

Prisma 1 generated SQL migration

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
role TEXT
);

Result of introspection in Prisma ORM 2.x and later versions

schema.prisma
model User {
id String @id
role String?
}

Workaround

You can manually turn the role column into an enum with your desired values

  1. Create an enum in your database that mirrors the enum you defined in the Prisma 1 datamodel
    CREATE TYPE "Role" AS ENUM ('CUSTOMER', 'ADMIN');
  2. Change the type from TEXT to your new enum
    ALTER TABLE "User" ALTER COLUMN "role" TYPE "Role"
    USING "role"::text::"Role";

After introspection, the type is now properly recognized as an enum

schema.prisma
model User {
id String @id
role Role?
}

enum Role {
ADMIN
CUSTOMER
}

Mismatching CUID length

Problem

Prisma 1 uses CUIDs as ID values for all database records. In the underlying database, these IDs are represented as strings with a maximum size of 25 characters (as VARCHAR(25)). However, when configuring default CUIDs in your Prisma ORM 2.x (or later versions) schema with @default(cuid()) the generated ID values might exceed the limit of 25 characters (the maximum length might be 30 characters). To make your IDs proof for Prisma ORM 2.x (or later versions), you therefore need to adjust the column type to VARCHAR(30).

Example

Prisma 1 datamodel

type User {
id: ID! @id
}

Prisma 1 generated SQL migration

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

Result of introspection in Prisma ORM 2.x and later versions

schema.prisma
model User {
id String @id
}

Workaround

You can manually turn the VARCHAR(25) columns into 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;

Note: When fixing this issue with the Upgrade CLI, the generated SQL statements will keep appearing in the Upgrade CLI even after you have changed the column types in the underlying database. This is a currently a limitation in the Upgrade CLI.

Scalar lists (arrays) are maintained with extra table

Problem

In Prisma 1, you can define lists of scalar types on your models. Under the hood, this is implemented with an extra table that keeps track of the values in the list.

To remove the approach with the extra table which incurred hidden performance costs, Prisma ORM 2.x and later versions only support scalar lists only when they're natively supported by the database you use. At the moment, only PostgreSQL supports scalar lists (arrays) natively

With PostgreSQL, you therefore can keep using scalar lists in Prisma ORM 2.x and later versions, but you'll need to perform a data migration to transfer the data from the extra table from Prisma 1 into an actual PostgreSQL array.

Example

Prisma 1 datamodel

type User {
id: ID! @id
coinflips: [Boolean!]! @scalarList(strategy: RELATION)
}

Prisma 1 generated SQL migration

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

Result of Prisma ORM 2 introspection

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

Note that you can now generate Prisma Client and you'll be able to access the data from the scalar lists through the extra table. PostgreSQL users can alternatively migrate the data into a native PostgreSQL array and continue to benefit from the slicker Prisma Client API for scalar lists (read the section below for more info).

Expand for sample Prisma Client API calls

To access the coinflips data, you will now have to always include it in your queries

const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
coinflips: {
orderBy: { position: 'asc' },
},
},
})

Note: The orderBy is important to retain the order of the list.

This is the `result of the query

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

To access just the boolean values from the list, you can map over the coinflips on user as follows

const currentCoinflips = user!.coinflips.map((cf) => cf.value)

Note: The exclamation mark above means that you're force unwrapping the user value. This is necessary because the user returned from the previous query might be null.

Here's the value of currentCoinflips after the call to map

[false, true, false, true, true, false]

Workaround

The following workaround is only available for PostgreSQL users!

As scalar lists (i.e. arrays) are available as a native PostgreSQL feature, you can keep using the same notation of coinflips: Boolean[] in your Prisma schema.

However, in order to do so you need to manually migrate the underlying data from the User_coinflips table into a PostgreSQL array. Here's how you can do that

  1. Add the new coinflips column to the User tables
    ALTER TABLE "User" ADD COLUMN coinflips BOOLEAN[];
  2. Migrate the data from "User_coinflips".value to "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. To cleanup, you can delete the User_coinflips table
    DROP TABLE "User_coinflips";

You can now introspect your database and the coinflips field will be represented as an array in your new Prisma schema

schema.prisma
model User {
id String @id
coinflips Boolean[]
}

You can keep using Prisma Client as before

const user = await prisma.user.findUnique({
where: { id: 1 },
})

This is the result from the API call

{
id: 1,
name: 'Alice',
coinflips: [ false, true, false, true, true, false ]
}