参照アクション
参照アクションとは、アプリケーションが関連レコードを削除または更新する際に、レコードに何が起こるかを決定するものです。
バージョン2.26.0以降では、Prismaスキーマのリレーションフィールドで参照アクションを定義できます。これにより、Prisma ORMレベルでカスケード削除やカスケード更新などの参照アクションを定義できます。
バージョンの違い
- バージョン3.0.1以降を使用している場合は、このページに記載されているとおりに参照アクションを使用できます。
- バージョン2.26.0から3.0.0の間を使用している場合は、このページに記載されているとおりに参照アクションを使用できますが、プレビュー機能フラグ
referentialActions
を有効にする必要があります。 - バージョン2.25.0以前を使用している場合は、データベースでカスケード削除を手動で設定できます。
以下の例では、Post
モデルのauthor
フィールドにonDelete: Cascade
を追加することで、User
レコードを削除すると、関連するすべてのPost
レコードも削除されることを意味します。
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
参照アクションを指定しない場合、Prisma ORMはデフォルトを使用します。
バージョン2.26.0以前からアップグレードする場合: 参照アクションのアップグレードパスのセクションをチェックすることが非常に重要です。Prisma ORMが参照アクションをサポートすることで、**ランタイムでカスケード削除を防いでいたPrisma Clientの安全ネットが削除されます**。**データベースをアップグレードせずに**この機能を使用すると、古いデフォルトアクション(ON DELETE CASCADE
)がアクティブになります。これにより、予期しないカスケード削除が発生する可能性があります。
参照アクションとは?
参照アクションは、update
またはdelete
クエリを実行する際に、参照されているレコードがデータベースによってどのように処理されるかを定義するポリシーです。
データベースレベルでの参照アクション
参照アクションは、データベースの参照整合性を維持するために存在する外部キー制約の機能です。
Prismaスキーマでデータモデル間のリレーションシップを定義する際、**データベースには存在しない**リレーションフィールドと、**データベースに存在する**スカラフィールドを使用します。これらの外部キーは、データベースレベルでモデルを接続します。
参照整合性とは、これらの外部キーが、関連するデータベーステーブル内の既存の主キー値を参照しなければならないという状態を指します。Prismaスキーマでは、これは通常、関連モデルのid
フィールドによって表現されます。
デフォルトでは、データベースは参照整合性に違反する操作、例えば参照されているレコードの削除などを拒否します。
参照アクションの使用方法
参照アクションは@relation
属性で定義され、基盤となるデータベースの**外部キー制約**のアクションにマッピングされます。参照アクションを指定しない場合、Prisma ORMはデフォルトにフォールバックします。
以下のモデルは、User
とPost
間の一対多リレーション、およびPost
とTag
間の多対多リレーションを明示的に定義された参照アクションとともに定義しています。
model User {
id Int @id @default(autoincrement())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
tags TagOnPosts[]
User User? @relation(fields: [userId], references: [id], onDelete: SetNull, onUpdate: Cascade)
userId Int?
}
model TagOnPosts {
id Int @id @default(autoincrement())
post Post? @relation(fields: [postId], references: [id], onUpdate: Cascade, onDelete: Cascade)
tag Tag? @relation(fields: [tagId], references: [id], onUpdate: Cascade, onDelete: Cascade)
postId Int?
tagId Int?
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts TagOnPosts[]
}
このモデルは、以下の参照アクションを明示的に定義しています。
Tag
を削除すると、Cascade
参照アクションを使用して、TagOnPosts
の対応するタグ割り当ても削除されます。User
を削除すると、SetNull
参照アクションにより、すべての投稿から作成者が削除され、フィールド値がNull
に設定されます。これを許可するには、Post
のUser
とuserId
がオプションフィールドである必要があります。
Prisma ORMは以下の参照アクションをサポートしています。
参照アクションのデフォルト
参照アクションを指定しない場合、Prisma ORMは以下のデフォルトを使用します。
条項 | オプションのリレーション | 必須のリレーション |
---|---|---|
onDelete | SetNull (NULL設定) | Restrict (制限) |
onUpdate | Cascade (カスケード) | Cascade (カスケード) |
例えば、以下のスキーマでは、すべてのPost
レコードはauthor
リレーションを介してUser
に接続されている必要があります。
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
このスキーマは、必須のauthor
リレーションフィールドで参照アクションを明示的に定義していません。これは、onDelete
にはデフォルトのRestrict
、onUpdate
にはデフォルトのCascade
が適用されることを意味します。
注意点
以下の注意点が適用されます。
- 暗黙的な多対多リレーションでは、参照アクションは**サポートされません**。参照アクションを使用するには、明示的な多対多リレーションを定義し、結合テーブルで参照アクションを定義する必要があります。
- 参照アクションと必須/オプションリレーションの特定の組み合わせは互換性がありません。たとえば、必須のリレーションで
SetNull
を使用すると、参照されているレコードを削除する際にデータベースエラーが発生します。これは、NULL非許容制約に違反するためです。詳細については、このGitHub Issueを参照してください。
参照アクションの種類
以下の表は、各データベースがサポートする参照アクションを示しています。
データベース | Cascade (カスケード) | Restrict (制限) | NoAction (アクションなし) | SetNull (NULL設定) | SetDefault (デフォルト設定) |
---|---|---|---|---|---|
PostgreSQL | ✔️ | ✔️ | ✔️ | ✔️⌘ | ✔️ |
MySQL/MariaDB | ✔️ | ✔️ | ✔️ | ✔️ | ❌ (✔️†) |
SQLite | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
SQL Server | ✔️ | ❌‡ | ✔️ | ✔️ | ✔️ |
CockroachDB | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
MongoDB†† | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
- † MySQLの特殊ケースを参照してください。
- ⌘ PostgreSQLの特殊ケースを参照してください。
- ‡ SQL Serverの特殊ケースを参照してください。
- †† MongoDBの参照アクションはPrisma ORMバージョン3.7.0以降で利用可能です。
参照アクションの特殊ケース
参照アクションはANSI SQL標準の一部です。ただし、一部のリレーショナルデータベースが標準から逸脱する特殊なケースがあります。
MySQL/MariaDB
MySQL/MariaDBおよび基盤となるInnoDBストレージエンジンは、SetDefault
をサポートしていません。正確な動作はデータベースのバージョンに依存します。
- MySQLバージョン8以降、およびMariaDBバージョン10.5以降では、
SetDefault
は実質的にNoAction
のエイリアスとして機能します。SET DEFAULT
参照アクションを使用してテーブルを定義できますが、ランタイムで外部キー制約エラーがトリガーされます。 - MySQLバージョン5.6以降、およびMariaDBバージョン10.5以前では、
SET DEFAULT
参照アクションでテーブル定義を作成しようとすると、構文エラーで失敗します。
このため、データベースプロバイダとしてmysql
を設定すると、Prisma ORMはPrismaスキーマのSetDefault
参照アクションを別のアクションに置き換えるようにユーザーに警告します。
PostgreSQL
PostgreSQLは、Prisma ORMがサポートする唯一のデータベースであり、NULL非許容フィールドを参照するSetNull
参照アクションを定義できます。ただし、アクションがランタイムでトリガーされると、外部キー制約エラーが発生します。
このため、(デフォルトの)foreignKeys
リレーションモードでpostgres
をデータベースプロバイダとして設定すると、Prisma ORMは、SetNull
参照アクションを持つ@relation
属性に含まれるすべてのフィールドをオプションとしてマークするようにユーザーに警告します。他のすべてのデータベースプロバイダでは、Prisma ORMは検証エラーでスキーマを拒否します。
SQL Server
SQL ServerデータベースではRestrict
は使用できませんが、代わりにNoAction
を使用できます。
Cascade
onDelete: Cascade
参照されているレコードを削除すると、参照しているレコードの削除がトリガーされます。onUpdate: Cascade
依存レコードの参照されているスカラフィールドが更新されると、リレーションのスカラフィールドも更新されます。
使用例
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
Cascade
を使用した結果
User
レコードが削除されると、その投稿も削除されます。ユーザーのid
が更新されると、対応するauthorId
も更新されます。
カスケード削除の使用方法
Restrict
onDelete: Restrict
参照しているレコードが存在する場合、削除を防止します。onUpdate: Restrict
参照されているレコードの識別子が変更されることを防止します。
使用例
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Restrict, onUpdate: Restrict)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
Restrict
を使用した結果
投稿を持つUser
は**削除できません**。User
のid
は**変更できません**。
Restrict
アクションはMicrosoft SQL Serverでは**利用できず**、スキーマ検証エラーをトリガーします。代わりに、同じ結果を生成しSQL Serverと互換性のあるNoAction
を使用できます。
NoAction
NoAction
アクションはRestrict
に似ていますが、両者の違いは使用するデータベースによって異なります。
- PostgreSQL:
NoAction
は(テーブル上の参照された行が存在するかどうかの)チェックをトランザクションの後半まで延期することを許可します。詳細については、PostgreSQLドキュメントを参照してください。 - MySQL:
NoAction
はRestrict
とまったく同じように動作します。詳細については、MySQLドキュメントを参照してください。 - SQLite: 関連する主キーが変更または削除されても、何もアクションは実行されません。詳細については、SQLiteドキュメントを参照してください。
- SQL Server: 参照されているレコードが削除または変更されると、エラーがスローされます。詳細については、SQL Serverドキュメントを参照してください。
- MongoDB (バージョン3.6.0以降でプレビュー): レコードが変更または削除されても、関連レコードには何も行われません。
データベースで外部キーを使用する代わりにPrisma Clientでリレーションを管理している場合、現在Prisma ORMは参照アクションのみを実装していることに注意してください。外部キーは制約も作成し、これらの制約に違反する方法でデータを操作することを不可能にします: クエリを実行する代わりに、データベースはエラーを返します。Prisma Clientで参照整合性をエミュレートする場合、これらの制約は作成されないため、参照アクションをNoAction
に設定した場合、参照整合性を破ることを防ぐチェックは行われません。
使用例
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
NoAction
を使用した結果
投稿を持つUser
は**削除できません**。User
のid
は**変更できません**。
SetNull
-
onDelete: SetNull
参照しているオブジェクトのスカラフィールドはNULL
に設定されます。 -
onUpdate: SetNull
参照されているオブジェクトの識別子を更新する際、参照しているオブジェクトのスカラフィールドはNULL
に設定されます。
SetNull
はオプションのリレーションでのみ機能します。必須のリレーションでは、スカラフィールドがNULLにできないため、ランタイムエラーがスローされます。
model Post {
id Int @id @default(autoincrement())
title String
author User? @relation(fields: [authorId], references: [id], onDelete: SetNull, onUpdate: SetNull)
authorId Int?
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
SetNull
を使用した結果
User
を削除すると、その作成したすべての投稿のauthorId
はNULL
に設定されます。
User
のid
を変更すると、その作成したすべての投稿のauthorId
はNULL
に設定されます。
SetDefault
-
onDelete: SetDefault
参照しているオブジェクトのスカラフィールドは、フィールドのデフォルト値に設定されます。 -
onUpdate: SetDefault
参照しているオブジェクトのスカラフィールドは、フィールドのデフォルト値に設定されます。
これには、@default
を使用してリレーションスカラフィールドにデフォルト値を設定する必要があります。いずれかのスカラフィールドにデフォルト値が指定されていない場合、ランタイムエラーがスローされます。
model Post {
id Int @id @default(autoincrement())
title String
authorUsername String? @default("anonymous")
author User? @relation(fields: [authorUsername], references: [username], onDelete: SetDefault, onUpdate: SetDefault)
}
model User {
username String @id
posts Post[]
}
SetDefault
を使用した結果
User
を削除すると、その既存の投稿のauthorUsername
フィールドの値は「anonymous」に設定されます。
User
のusername
が変更されると、その既存の投稿のauthorUsername
フィールドの値は「anonymous」に設定されます。
データベース固有の要件
MongoDBとSQL Serverには、データモデルに自己リレーションまたは循環リレーションがある場合、参照アクションに関する特定の要件があります。SQL Serverには、複数カスケードパスを持つリレーションがある場合にも特定の要件があります。
バージョン2.25.0以前からのアップグレードパス
アップグレード時にはいくつかのパスがあり、望む結果に応じて異なる結果が得られます。
現在マイグレーションワークフローを使用している場合、イントロスペクションを実行して、デフォルトがスキーマにどのように反映されているかを確認できます。必要に応じて、データベースを手動で更新できます。
また、デフォルトの確認をスキップし、マイグレーションを実行して新しいデフォルト値でデータベースを更新することもできます。
以下は、バージョン2.26.0以降にアップグレードし、プレビュー機能フラグを有効にしたか、またはバージョン3.0.0以降にアップグレードしたことを前提としています。
イントロスペクションの使用
データベースをイントロスペクトすると、データベースレベルで設定された参照アクションがPrismaスキーマに反映されます。Prisma Migrateまたはprisma db push
を使用してデータベーススキーマを管理してきた場合、これらは2.25.0以前のデフォルト値である可能性が高いです。
イントロスペクションを実行すると、Prisma ORMはデータベース内のすべての外部キーをスキーマと比較します。SQLステートメントのON DELETE
とON UPDATE
がデフォルト値に**一致しない**場合、それらはスキーマファイルに明示的に設定されます。
イントロスペクト後、スキーマ内の非デフォルト条項を確認できます。確認する最も重要な条項はonDelete
で、これは2.25.0以前ではデフォルトでCascade
です。
delete()
またはdeleteMany()
メソッドを使用している場合、referentialActions
プレビュー機能が**以前はランタイムでのカスケード削除を防いでいたPrisma Clientの安全ネットを削除した**ため、**カスケード削除が実行されるようになります**。コードを確認し、それに応じて調整してください。
スキーマ内のonDelete: Cascade
のすべてのケースに問題がないことを確認してください。問題がある場合は、次のいずれかを実行してください。
- Prismaスキーマを修正し、
db push
またはdev migrate
を実行してデータベースを変更します
または
- イントロスペクションのみのワークフローを使用している場合は、基盤となるデータベースを手動で更新します。
以下の例では、User
が削除された場合、そのすべてのPost
も削除されるカスケード削除が発生します。
ブログスキーマの例
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
マイグレーションの使用
マイグレーション(またはprisma db push
コマンド)を実行すると、新しいデフォルトがデータベースに適用されます。
初めてイントロスペクトを実行する場合とは異なり、Prisma VSCode拡張機能によって、新しい参照アクションの句とプロパティがPrismaスキーマに自動的に追加されることは**ありません**。新しいデフォルト以外のものを使用したい場合は、手動で追加する必要があります。
Prismaスキーマで参照アクションを明示的に定義することはオプションです。リレーションの参照アクションを明示的に定義しない場合、Prisma ORMは新しいデフォルトを使用します。
参照アクションはケースバイケースで追加できることに注意してください。つまり、手動で何も指定しないことで、1つのリレーションに追加し、残りはデフォルト設定のままにすることができます。
エラーの確認
バージョン2.26.0にアップグレードし、参照アクションの**プレビュー機能**を有効にする**前は**、Prisma ORMは参照整合性を保持するためにdelete()
またはdeleteMany()
を使用する際のレコードの削除を防止していました。エラーコードP2014
を伴うカスタムランタイムエラーがPrisma Clientによってスローされていました。
参照アクションの**プレビュー機能**をアップグレードして有効にした**後は**、Prisma ORMはランタイムチェックを実行しなくなります。代わりに、リレーション間の参照整合性を保持するためにカスタム参照アクションを指定できます。
レコードの削除を防止するためにNoAction
またはRestrict
を使用する場合、エラーメッセージは2.26.0以前と比較して2.26.0以降では異なります。これは、エラーがPrisma Clientによってではなく、データベースによってトリガーされるためです。予期される新しいエラーコードはP2003
です。
これらの新しいエラーを捕捉するために、コードをそれに応じて調整できます。
エラーを捕捉する例
以下の例では、Post
とUser
の間の一対多リレーションシップを持つ以下のブログスキーマを使用し、author
フィールドにRestrict
参照アクションを設定しています。
これは、ユーザーが投稿を持っている場合、そのユーザー(およびその投稿)を**削除できない**ことを意味します。
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Restrict)
authorId String
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
参照アクションの**プレビュー機能**をアップグレードして有効にする前は、投稿を持つユーザーを削除しようとしたときに受け取るエラーコードはP2014
であり、そのメッセージは
"変更しようとしている操作は、{model_a_name}モデルと{model_b_name}モデル間の必須リレーション'{relation_name}'に違反します。"
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
try {
await prisma.user.delete({
where: {
id: 'some-long-id',
},
})
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2014') {
console.log(error.message)
}
}
}
}
main()
コードで正しいエラーをチェックしていることを確認するために、チェックを修正してP2003
を探してください。これは以下のメッセージを返します。
"フィールド: {field_name}で外部キー制約に失敗しました。"
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
try {
await prisma.user.delete({
where: {
id: 'some-long-id'
}
})
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2014') {
if (error.code === 'P2003') {
console.log(error.message)
}
}
}
}
main()