Prismaのincrementが正しく動作しなかった原因
はじめに
筆者が関わっているプロジェクトでは、 TypeScript + MySQL + Prismaを利用しているものがあります。
その実装においてPrismaを使用してデータベースの操作を行う際、increment 操作が予想通りに動作しない事象に遭遇したため、本記事ではその原因と対処法について記録していきます。
コードと挙動のイメージ
incrementと言っているのは以下のような動作をイメージしています。
当初の筆者の頭の中ではSampleのcountを 1
にしようとしているのに null
になっておかしいと誤解してしまいました。
model Sample {
id Int @id
count Int?
}
const updateSample = await prisma.sample.update({
where: { id },
data: {
count: { increment: 3 }
}
});
=> countが 3 になると思っていたが、null になってしまった
問題の原因
多くのリレーショナルデータベースのSQLの標準的な仕様として、null + 1
をしても null
のままになります。改めて考えると当たり前なことですがここを誤解してしまっていました。
この誤解を筆者がしてしまった理由の一員として、JavaScriptでは null + 1
が 1
になる挙動をしてしまうという点がありました。
PrimsaはJavaScriptで利用するライブラリのため、prismaのincrementもよしなに加算してくれるものだと直感的に解釈し誤解をしてしまいました。
解決策
defaultを0にする
最も簡単そうなアプローチは、フィールドのデフォルト値を 0
に設定し、null
を拒否することです。
これにより、新規のcountフィールドが null
ではなく 0
から開始されるようになるのでこの挙動をそもそも意識することがなくなります。
model Sample {
id Int @id
count Int @default(0)
}
アプリケーション側で条件付きロジックで計算
集計処理が実際に行われるまでは null
を許容したいなどの要件がある場合は、アプリケーションのロジックで null
を適切に扱うような設計が必要になります。
特定のフィールドに対して加算操作を行う前に、そのフィールドが null
であるかどうかを確認し、必要に応じてincrimentするか直接値を設定するかを算出します。
async function incrementCount(id: number, increment: number ) {
const sample = await prisma.sample.findUnique({
where: { id }
});
if (sample.count === null) {
return prisma.sample.update({
where: { id },
data: {
count: increment
}
});
}
return prisma.sample.update({
where: { id },
data: {
count: { increment }
}
});
}
このような設計では、データベースへのアクセス回数が増える可能性があり、パフォーマンスに影響を与える場合があります。また、集計を開始する前の状態を明確にするために null
を使うかどうかなどについても考慮しておく必要がありそうです。
おわりに
内容としてはシンプルでわざわざ書くようなことではなかったかもしれないですが、初学者の方や将来の自分が忘れて同じミスをしてしまうこともあると思い備忘録として執筆しました。
本記事が今後の自分を含め誰かのサポートになると幸いです。
ユーザーファーストなサービスを伴に考えながらつくる、デザインとエンジニアリングの会社です。エンジニア積極採用中です!hrmos.co/pages/funteractive/jobs
Discussion