メインコンテンツにスキップ

参照アクション

参照アクションとは、アプリケーションが関連レコードを削除または更新する際に、レコードに何が起こるかを決定するものです。

バージョン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レコードも削除されることを意味します。

schema.prisma
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はデフォルトにフォールバックします

以下のモデルは、UserPost間の一対多リレーション、およびPostTag間の多対多リレーションを明示的に定義された参照アクションとともに定義しています。

schema.prisma
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に設定されます。これを許可するには、PostUseruserIdがオプションフィールドである必要があります。

Prisma ORMは以下の参照アクションをサポートしています。

参照アクションのデフォルト

参照アクションを指定しない場合、Prisma ORMは以下のデフォルトを使用します。

条項オプションのリレーション必須のリレーション
onDeleteSetNull (NULL設定)Restrict (制限)
onUpdateCascade (カスケード)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にはデフォルトのRestrictonUpdateにはデフォルトのCascadeが適用されることを意味します。

注意点

以下の注意点が適用されます。

  • 暗黙的な多対多リレーションでは、参照アクションは**サポートされません**。参照アクションを使用するには、明示的な多対多リレーションを定義し、結合テーブルで参照アクションを定義する必要があります。
  • 参照アクションと必須/オプションリレーションの特定の組み合わせは互換性がありません。たとえば、必須のリレーションでSetNullを使用すると、参照されているレコードを削除する際にデータベースエラーが発生します。これは、NULL非許容制約に違反するためです。詳細については、このGitHub Issueを参照してください。

参照アクションの種類

以下の表は、各データベースがサポートする参照アクションを示しています。

データベースCascade (カスケード)Restrict (制限)NoAction (アクションなし)SetNull (NULL設定)SetDefault (デフォルト設定)
PostgreSQL✔️✔️✔️✔️⌘✔️
MySQL/MariaDB✔️✔️✔️✔️❌ (✔️†)
SQLite✔️✔️✔️✔️✔️
SQL Server✔️❌‡✔️✔️✔️
CockroachDB✔️✔️✔️✔️✔️
MongoDB††✔️✔️✔️✔️

参照アクションの特殊ケース

参照アクションは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 依存レコードの参照されているスカラフィールドが更新されると、リレーションのスカラフィールドも更新されます。

使用例

schema.prisma
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 参照されているレコードの識別子が変更されることを防止します。

使用例

schema.prisma
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は**削除できません**。Useridは**変更できません**。

警告

RestrictアクションはMicrosoft SQL Serverでは**利用できず**、スキーマ検証エラーをトリガーします。代わりに、同じ結果を生成しSQL Serverと互換性のあるNoActionを使用できます。

NoAction

NoActionアクションはRestrictに似ていますが、両者の違いは使用するデータベースによって異なります。

  • PostgreSQL: NoActionは(テーブル上の参照された行が存在するかどうかの)チェックをトランザクションの後半まで延期することを許可します。詳細については、PostgreSQLドキュメントを参照してください。
  • MySQL: NoActionRestrictとまったく同じように動作します。詳細については、MySQLドキュメントを参照してください。
  • SQLite: 関連する主キーが変更または削除されても、何もアクションは実行されません。詳細については、SQLiteドキュメントを参照してください。
  • SQL Server: 参照されているレコードが削除または変更されると、エラーがスローされます。詳細については、SQL Serverドキュメントを参照してください。
  • MongoDB (バージョン3.6.0以降でプレビュー): レコードが変更または削除されても、関連レコードには何も行われません。
警告

データベースで外部キーを使用する代わりにPrisma Clientでリレーションを管理している場合、現在Prisma ORMは参照アクションのみを実装していることに注意してください。外部キーは制約も作成し、これらの制約に違反する方法でデータを操作することを不可能にします: クエリを実行する代わりに、データベースはエラーを返します。Prisma Clientで参照整合性をエミュレートする場合、これらの制約は作成されないため、参照アクションをNoActionに設定した場合、参照整合性を破ることを防ぐチェックは行われません。

使用例

schema.prisma
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は**削除できません**。Useridは**変更できません**。

SetNull

  • onDelete: SetNull 参照しているオブジェクトのスカラフィールドはNULLに設定されます。

  • onUpdate: SetNull 参照されているオブジェクトの識別子を更新する際、参照しているオブジェクトのスカラフィールドはNULLに設定されます。

SetNullはオプションのリレーションでのみ機能します。必須のリレーションでは、スカラフィールドがNULLにできないため、ランタイムエラーがスローされます。

schema.prisma
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を削除すると、その作成したすべての投稿のauthorIdNULLに設定されます。

Useridを変更すると、その作成したすべての投稿のauthorIdNULLに設定されます。

SetDefault

  • onDelete: SetDefault 参照しているオブジェクトのスカラフィールドは、フィールドのデフォルト値に設定されます。

  • onUpdate: SetDefault 参照しているオブジェクトのスカラフィールドは、フィールドのデフォルト値に設定されます。

これには、@defaultを使用してリレーションスカラフィールドにデフォルト値を設定する必要があります。いずれかのスカラフィールドにデフォルト値が指定されていない場合、ランタイムエラーがスローされます。

schema.prisma
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」に設定されます。

Userusernameが変更されると、その既存の投稿の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 DELETEON 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です。

これらの新しいエラーを捕捉するために、コードをそれに応じて調整できます。

エラーを捕捉する例

以下の例では、PostUserの間の一対多リレーションシップを持つ以下のブログスキーマを使用し、authorフィールドにRestrict参照アクションを設定しています。

これは、ユーザーが投稿を持っている場合、そのユーザー(およびその投稿)を**削除できない**ことを意味します。

schema.prisma
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()
© . All rights reserved.