null と undefined
Prisma ORM では、`undefined` が値として渡された場合、生成されるクエリには含まれません。この動作は、予期せぬ結果やデータ損失につながる可能性があります。これを防ぐため、後述する新しい `strictUndefinedChecks` プレビュー機能を活用できるよう、バージョン 5.20.0 以降へのアップデートを強くお勧めします。
現在の動作 (`strictUndefinedChecks` プレビュー機能なし) についてのドキュメントは、現在の動作 を参照してください。
厳密な undefined チェック (プレビュー機能)
Prisma ORM 5.20.0 では、`strictUndefinedChecks` と呼ばれる新しいプレビュー機能が導入されました。この機能は、Prisma Client が `undefined` 値を処理する方法を変更し、偶発的なデータ損失や意図しないクエリ動作に対する保護を強化します。
厳密な undefined チェックを有効にする
この機能を有効にするには、Prisma スキーマに以下を追加します
generator client {
provider = "prisma-client-js"
previewFeatures = ["strictUndefinedChecks"]
}
厳密な undefined チェックの使用
この機能が有効になっている場合
- クエリでフィールドを明示的に `undefined` に設定すると、実行時エラーが発生します。
- クエリでフィールドをスキップするには、`undefined` の代わりに新しい `Prisma.skip` シンボルを使用します。
使用例
// This will throw an error
prisma.user.create({
data: {
name: 'Alice',
email: undefined // Error: Cannot explicitly use undefined here
}
})
// Use `Prisma.skip` (a symbol provided by Prisma) to omit a field
prisma.user.create({
data: {
name: 'Alice',
email: Prisma.skip // This field will be omitted from the query
}
})
この変更は、偶発的な削除や更新を防ぐのに役立ちます。例えば:
// Before: This would delete all users
prisma.user.deleteMany({
where: {
id: undefined
}
})
// After: This will throw an error
Invalid \`prisma.user.deleteMany()\` invocation in
/client/tests/functional/strictUndefinedChecks/test.ts:0:0
XX })
XX
XX test('throws on undefined input field', async () => {
→ XX const result = prisma.user.deleteMany({
where: {
id: undefined
~~~~~~~~~
}
})
Invalid value for argument \`where\`: explicitly \`undefined\` values are not allowed."
移行パス
既存のコードを移行するには
// Before
let optionalEmail: string | undefined
prisma.user.create({
data: {
name: 'Alice',
email: optionalEmail
}
})
// After
prisma.user.create({
data: {
name: 'Alice',
email: optionalEmail ?? Prisma.skip
}
})
`exactOptionalPropertyTypes`
`strictUndefinedChecks` に加えて、TypeScript のコンパイラオプション `exactOptionalPropertyTypes` を有効にすることも推奨します。このオプションは、オプショナルプロパティが厳密に一致することを強制し、コード内の `undefined` 値に関する潜在的な問題を検出するのに役立ちます。`strictUndefinedChecks` が無効な `undefined` の使用に対して実行時エラーを発生させるのに対し、`exactOptionalPropertyTypes` はビルドプロセス中にこれらの問題を検出します。
`exactOptionalPropertyTypes` については、TypeScript ドキュメント で詳しく学ぶことができます。
フィードバック
この機能に関する皆様からのフィードバックはいつでも歓迎いたします。このプレビュー機能に関する GitHub ディスカッション でご意見やご提案をお寄せください。
現在の動作
Prisma Client は `null` と `undefined` を区別します
- `null` は 値 です
- `undefined` は 何もしない ことを意味します
これは、`null` と `undefined` が互換性を持つ GraphQL コンテキストにおける Prisma ORM で特に考慮すべき重要な点です。
以下のデータは `User` テーブルを表しています。このデータセットは、以下のすべての例で使用されます。
id | name | |
---|---|---|
1 | Nikolas | nikolas@gmail.com |
2 | Martin | martin@gmail.com |
3 | empty | sabin@gmail.com |
4 | Tyler | tyler@gmail.com |
複数の レコードに影響するクエリにおける `null` と `undefined`
このセクションでは、`undefined` と `null` の値が、データベース内で複数のレコードを操作または作成するクエリの動作にどのように影響するかについて説明します。
null
次の Prisma Client クエリについて考えてみましょう。これは、`name` の値が提供された `null` と一致するすべてのユーザーを検索します。
const users = await prisma.user.findMany({
where: {
name: null,
},
})
[
{
"id": 3,
"name": null,
"email": "sabin@gmail.com"
}
]
`name` カラムのフィルターとして `null` が指定されたため、Prisma Client は `User` テーブル内で `name` カラムが 空 のすべてのレコードを検索するクエリを生成します。
undefined
では、`name` カラムで `undefined` をフィルター値として同じクエリを実行するシナリオを考えてみましょう。
const users = await prisma.user.findMany({
where: {
name: undefined,
},
})
[
{
"id": 1,
"name": "Nikolas",
"email": "nikolas@gmail.com"
},
{
"id": 2,
"name": "Martin",
"email": "martin@gmail.com"
},
{
"id": 3,
"name": null,
"email": "sabin@gmail.com"
},
{
"id": 4,
"name": "Tyler",
"email": "tyler@gmail.com"
}
]
フィルターで `undefined` を値として使用すると、基本的に Prisma Client にそのカラムの フィルターを定義しない ことを決定したと伝えます。
上記のクエリを同等に記述する方法は次のとおりです。
const users = await prisma.user.findMany()
このクエリは、`User` テーブルからすべての行を選択します。
注: Prisma Client クエリのパラメータオブジェクト内の任意のキーの値として `undefined` を使用すると、Prisma ORM はそのキーがまったく提供されなかったかのように動作します。
このセクションの例は `findMany` 関数に焦点を当てていますが、`updateMany` や `deleteMany` のように複数のレコードに影響を与える可能性のあるすべての関数に同じ概念が適用されます。
単一の レコードに影響するクエリにおける `null` と `undefined`
このセクションでは、`undefined` と `null` の値が、データベース内で単一のレコードを操作または作成するクエリの動作にどのように影響するかについて説明します。
注: `null` は `findUnique()` クエリの有効なフィルター値ではありません。
単一のレコードに影響するクエリのフィルター条件で `null` と `undefined` を使用した場合のクエリ動作は、前のセクションで説明した動作と非常によく似ています。
null
`name` カラムをフィルターするために `null` が使用されている次のクエリについて考えてみましょう。
const user = await prisma.user.findFirst({
where: {
name: null,
},
})
[
{
"id": 3,
"name": null,
"email": "sabin@gmail.com"
}
]
`name` カラムのフィルターとして `null` が使用されたため、Prisma Client は `User` テーブル内で `name` の値が 空 の最初のレコードを検索するクエリを生成します。
undefined
代わりに `undefined` が `name` カラムのフィルター値として使用された場合、そのカラムにフィルター条件がまったく渡されなかったかのようにクエリが動作します。
以下のクエリを考えてみましょう
const user = await prisma.user.findFirst({
where: {
name: undefined,
},
})
[
{
"id": 1,
"name": "Nikolas",
"email": "nikolas@gmail.com"
}
]
このシナリオでは、クエリはデータベースの最初のレコードを返します。
上記のクエリを表す別の方法は次のとおりです
const user = await prisma.user.findFirst()
このセクションの例は `findFirst` 関数に焦点を当てていますが、単一のレコードに影響を与えるすべての関数に同じ概念が適用されます。
GraphQL リゾルバーにおける `null` と `undefined`
この例では、以下の Prisma スキーマに基づいたデータベースを考えてみましょう。
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
ユーザーを更新する以下の GraphQL ミューテーションでは、`authorEmail` と `name` の両方が `null` を受け入れます。GraphQL の観点からは、これはフィールドが オプション であることを意味します。
type Mutation {
// Update author's email or name, or both - or neither!
updateUser(id: Int!, authorEmail: String, authorName: String): User!
}
しかし、`authorEmail` または `authorName` に `null` 値を Prisma Client に渡した場合、次のようなことが起こります。
- `args.authorEmail` が `null` の場合、クエリは 失敗します。`email` は `null` を受け入れません。
- `args.authorName` が `null` の場合、Prisma Client は `name` の値を `null` に変更します。これはおそらく、更新が機能してほしい方法ではありません。
updateUser: (parent, args, ctx: Context) => {
return ctx.prisma.user.update({
where: { id: Number(args.id) },
data: {
email: args.authorEmail, // email cannot be null
name: args.authorName // name set to null - potentially unwanted behavior
},
})
},
代わりに、入力値が `null` の場合、`email` と `name` の値を `undefined` に設定します。こうすることで、フィールドをまったく更新しないのと同じことになります。
updateUser: (parent, args, ctx: Context) => {
return ctx.prisma.user.update({
where: { id: Number(args.id) },
data: {
email: args.authorEmail != null ? args.authorEmail : undefined, // If null, do nothing
name: args.authorName != null ? args.authorName : undefined // If null, do nothing
},
})
},
条件付き処理における `null` と `undefined` の影響
条件付きでフィルタリングする際に、予期せぬ結果が生じる可能性がある注意点があります。Prisma Client がヌル許容値をどのように扱うかを考慮すると、条件付きでフィルタリングする際に期待する結果と異なる結果が得られる場合があります。
次の表は、異なる演算子が0個、1個、および `n` 個のフィルターをどのように処理するかを大まかに示しています。
演算子 | 0個のフィルター | 1個のフィルター | n個のフィルター |
---|---|---|---|
OR | 空のリストを返す | 単一のフィルターを検証 | 全てのフィルターを検証 |
AND | 全てのアイテムを返す | 単一のフィルターを検証 | 全てのフィルターを検証 |
NOT | 全てのアイテムを返す | 単一のフィルターを検証 | 全てのフィルターを検証 |
この例は、OR
演算子を使用するクエリが、`undefined` パラメータによって返される結果にどのように影響されるかを示しています。
interface FormData {
name: string
email?: string
}
const formData: FormData = {
name: 'Emelie',
}
const users = await prisma.user.findMany({
where: {
OR: [
{
email: {
contains: formData.email,
},
},
],
},
})
// returns: []
このクエリは、オプションの email プロパティを含む formData オブジェクトからフィルターを受け取ります。このインスタンスでは、email プロパティの値は `undefined` です。このクエリを実行してもデータは返されません。
これは、`undefined` 値を渡した場合にすべてのユーザーを返す AND
演算子と NOT
演算子とは対照的です。
これは、`AND` または `NOT` 演算子に `undefined` 値を渡すことは、何も渡さないことと同じであり、例の `findMany` クエリがフィルターなしで実行され、すべてのユーザーを返すためです。
interface FormData {
name: string
email?: string
}
const formData: FormData = {
name: 'Emelie',
}
const users = await prisma.user.findMany({
where: {
AND: [
{
email: {
contains: formData.email,
},
},
],
},
})
// returns: { id: 1, email: 'ems@boop.com', name: 'Emelie' }
const users = await prisma.user.findMany({
where: {
NOT: [
{
email: {
contains: formData.email,
},
},
],
},
})
// returns: { id: 1, email: 'ems@boop.com', name: 'Emelie' }