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

Null と Undefined

warning

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 チェックの使用

この機能が有効になっている場合

  1. クエリでフィールドを明示的に undefined に設定すると、ランタイムエラーが発生します。
  2. クエリでフィールドをスキップするには、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 は nullundefined を区別します。

  • null です。
  • undefined何もしない ことを意味します。
info

これは、GraphQL コンテキストを持つ Prisma ORM で特に重要です。ここでは、nullundefined は互換性があります。

以下のデータは、User テーブルを表しています。このデータセットは、以下のすべての例で使用されます。

多数 のレコードに影響を与えるクエリにおける nullundefined

このセクションでは、undefined 値と null 値が、データベース内の複数のレコードを操作または作成するクエリの動作にどのように影響するかについて説明します。

Null

以下の Prisma Client クエリについて考えてみましょう。これは、name 値が提供された null 値と一致するすべてのユーザーを検索します。

const users = await prisma.user.findMany({
where: {
name: null,
},
})
表示クエリ結果
[
{
"id": 3,
"name": null,
"email": "sabin@gmail.com"
}
]

nullname 列のフィルターとして提供されたため、Prisma Client は、name 列がである User テーブル内のすべてのレコードを検索するクエリを生成します。

Undefined

次に、同じクエリを undefinedname 列のフィルター値として実行するシナリオについて考えてみましょう。

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 テーブルからすべての行を選択します。

info

注意: Prisma Client クエリのパラメータオブジェクト内の任意のキーの値として undefined を使用すると、Prisma ORM はそのキーがまったく提供されなかったかのように動作します。

このセクションの例は findMany 関数に焦点を当てていますが、同じ概念は、updateManydeleteMany など、複数のレコードに影響を与える可能性のある任意の関数に適用されます。

1 つ のレコードに影響を与えるクエリにおける nullundefined

このセクションでは、undefined 値と null 値が、データベース内の単一のレコードを操作または作成するクエリの動作にどのように影響するかについて説明します。

warning

注意: nullfindUnique() クエリでは有効なフィルター値ではありません。

単一のレコードに影響を与えるクエリのフィルター条件で nullundefined を使用した場合のクエリの動作は、前のセクションで説明した動作と非常によく似ています。

Null

nullname 列をフィルタリングするために使用される次のクエリについて考えてみましょう。

const user = await prisma.user.findFirst({
where: {
name: null,
},
})
表示クエリ結果
[
{
"id": 3,
"name": null,
"email": "sabin@gmail.com"
}
]

nullname 列のフィルターとして使用されたため、Prisma Client は、name 値がである User テーブル内の最初のレコードを検索するクエリを生成します。

Undefined

代わりに undefinedname 列のフィルター値として使用された場合、クエリはその列にフィルター条件がまったく渡されなかったかのように動作します

以下のクエリについて考えてみましょう。

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 リゾルバーにおける nullundefined

この例では、次の Prisma スキーマに基づくデータベースについて考えてみましょう。

model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}

ユーザーを更新する次の GraphQL ミューテーションでは、authorEmailname の両方が null を受け入れます。GraphQL の観点から見ると、これはフィールドがオプションであることを意味します。

type Mutation {
// Update author's email or name, or both - or neither!
updateUser(id: Int!, authorEmail: String, authorName: String): User!
}

ただし、authorEmail または authorNamenull 値を Prisma Client に渡すと、次のことが起こります。

  • args.authorEmailnull の場合、クエリは失敗します。emailnull を受け入れません。
  • args.authorNamenull の場合、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 の場合は、emailname の値を 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
},
})
},

条件付きステートメントにおける nullundefined の効果

条件付きステートメントでフィルタリングする場合、予期しない結果が生じる可能性のある注意点がいくつかあります。条件付きステートメントでフィルタリングする場合、Prisma Client が nullable 値をどのように扱うかを考えると、ある結果を期待しても別の結果を受け取る可能性があります。

次の表は、さまざまな演算子が 0、1、および n フィルターをどのように処理するかの概要を示しています。

演算子0 フィルター1 フィルターn フィルター
OR空のリストを返す単一フィルターを検証するすべてのフィルターを検証する
ANDすべてのアイテムを返す単一フィルターを検証するすべてのフィルターを検証する
NOTすべてのアイテムを返す単一フィルターを検証するすべてのフィルターを検証する

この例は、undefined パラメータが OR 演算子を使用するクエリによって返される結果にどのように影響するかを示しています。

interface FormData {
name: string
email?: string
}

const formData: FormData = {
name: 'Emelie',
}

const users = await prisma.user.findMany({
where: {
OR: [
{
email: {
contains: formData.email,
},
},
],
},
})

// returns: []

クエリは formData オブジェクトからフィルターを受け取ります。これには、オプションの email プロパティが含まれています。このインスタンスでは、email プロパティの値は undefined です。このクエリが実行されると、データは返されません。

これは、AND および NOT 演算子とは対照的です。AND または NOT 演算子に undefined 値を渡すと、両方ともすべてのユーザーを返します。

これは、undefined 値を AND または NOT 演算子に渡すことは、何も渡さないことと同じであるためです。つまり、例の 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' }