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
は 何もしない ことを意味します。
これは、GraphQL コンテキストを持つ Prisma ORM で特に重要です。ここでは、null
と undefined
は互換性があります。
以下のデータは、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"
}
]
null
が name
列のフィルターとして提供されたため、Prisma Client は、name
列が空である User
テーブル内のすべてのレコードを検索するクエリを生成します。
Undefined
次に、同じクエリを undefined
を name
列のフィルター値として実行するシナリオについて考えてみましょう。
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
など、複数のレコードに影響を与える可能性のある任意の関数に適用されます。
1 つ のレコードに影響を与えるクエリにおける null
と undefined
このセクションでは、undefined
値と null
値が、データベース内の単一のレコードを操作または作成するクエリの動作にどのように影響するかについて説明します。
注意: null
は findUnique()
クエリでは有効なフィルター値ではありません。
単一のレコードに影響を与えるクエリのフィルター条件で null
と undefined
を使用した場合のクエリの動作は、前のセクションで説明した動作と非常によく似ています。
Null
null
が name
列をフィルタリングするために使用される次のクエリについて考えてみましょう。
const user = await prisma.user.findFirst({
where: {
name: null,
},
})
[
{
"id": 3,
"name": null,
"email": "sabin@gmail.com"
}
]
null
が name
列のフィルターとして使用されたため、Prisma Client は、name
値が空である User
テーブル内の最初のレコードを検索するクエリを生成します。
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 が 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' }