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 及更高版本中內省的結果
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)
}
手動將 @default
屬性新增到 Prisma 模型
您可以將 @default
屬性新增到 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 及更高版本中內省的結果
model Post {
id String @id
}
由於資料庫中沒有 CUID 行為的指示,因此 Prisma ORM 的內省功能無法識別它。
解決方案
作為一種解決方案,您可以手動將 @default(cuid())
屬性新增到 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 及更高版本中內省的結果
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 結構描述中設定了 @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 及更高版本中內省的結果
model Post {
id String @id
updatedAt DateTime
}
解決方案
手動將 @updatedAt
屬性新增到 Prisma 模型
作為一種解決方案,您可以手動將 @updatedAt
屬性新增到 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 及更高版本中內省的結果
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 關聯將被正確識別
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 及更高版本中內省的結果
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 關聯相容的結構中
- 在
Post
表格上建立新的資料行authorId
。此資料行應為參考User
表格的id
欄位的外來鍵ALTER TABLE `Post` ADD COLUMN `authorId` VARCHAR(25);
ALTER TABLE `Post` ADD FOREIGN KEY (`authorId`) REFERENCES `User` (`id`); - 撰寫一個 SQL 查詢,從
_PostToUser
關聯表中讀取所有列,並針對每一列- 透過查閱資料行
A
中的值來尋找各自的Post
記錄 - 將資料行
B
中的值作為authorId
的值插入該Post
記錄
UPDATE Post, _PostToUser
SET Post.authorId = _PostToUser.B
WHERE Post.id = _PostToUser.A - 透過查閱資料行
- 刪除
_PostToUser
關聯表DROP TABLE `_PostToUser`;
之後,您可以內省您的資料庫,並且關聯現在將被識別為 1-n
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
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
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
model User {
id String @id
role String?
}
Workaround
You can manually turn the role
column into an enum with your desired values
- Create an
enum
in your database that mirrors theenum
you defined in the Prisma 1 datamodelCREATE TYPE "Role" AS ENUM ('CUSTOMER', 'ADMIN');
- Change the type from
TEXT
to your newenum
ALTER TABLE "User" ALTER COLUMN "role" TYPE "Role"
USING "role"::text::"Role";
After introspection, the type is now properly recognized as an enum
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
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
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 theuser
returned from the previous query might benull
.
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
- Add the new
coinflips
column to theUser
tablesALTER TABLE "User" ADD COLUMN coinflips BOOLEAN[];
- 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"; - To cleanup, you can delete the
User_coinflips
tableDROP TABLE "User_coinflips";
You can now introspect your database and the coinflips
field will be represented as an array in your new Prisma schema
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 ]
}