参照整合性アクション
参照整合性アクションは、アプリケーションが関連レコードを削除または更新したときにレコードに何が起こるかを決定します。
バージョン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
の間の1対多リレーションと、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 | 制限 |
onUpdate | カスケード | カスケード |
たとえば、次のスキーマでは、すべての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を参照してください。
参照整合性アクションの種類
次の表は、各データベースがサポートする参照整合性アクションを示しています。
データベース | カスケード | 制限 | NoAction | SetNull | 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
Restrict
はSQL Serverデータベースでは利用できませんが、代わりに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
に似ていますが、2つの違いは使用されているデータベースによって異なります
- 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
の間の1対多リレーションシップを持つ以下のブログスキーマを使用し、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()