🫣

Prismaのincrementが正しく動作しなかった原因

2024/09/19に公開

はじめに

筆者が関わっているプロジェクトでは、 TypeScript + MySQL + Prismaを利用しているものがあります。
その実装においてPrismaを使用してデータベースの操作を行う際、increment 操作が予想通りに動作しない事象に遭遇したため、本記事ではその原因と対処法について記録していきます。

コードと挙動のイメージ

incrementと言っているのは以下のような動作をイメージしています。
当初の筆者の頭の中ではSampleのcountを 1 にしようとしているのに null になっておかしいと誤解してしまいました。

prisma.schema
model Sample {
  id     Int    @id
  count  Int?   
}
sample.ts
const updateSample = await prisma.sample.update({
  where: { id },
  data: {
    count: { increment: 3 }
  }
});

=> countが 3 になると思っていたが、null になってしまった 

問題の原因

多くのリレーショナルデータベースのSQLの標準的な仕様として、null + 1 をしても nullのままになります。改めて考えると当たり前なことですがここを誤解してしまっていました。
この誤解を筆者がしてしまった理由の一員として、JavaScriptでは null + 11 になる挙動をしてしまうという点がありました。

PrimsaはJavaScriptで利用するライブラリのため、prismaのincrementもよしなに加算してくれるものだと直感的に解釈し誤解をしてしまいました。

解決策

defaultを0にする

最も簡単そうなアプローチは、フィールドのデフォルト値を 0 に設定し、null を拒否することです。
これにより、新規のcountフィールドが null ではなく 0 から開始されるようになるのでこの挙動をそもそも意識することがなくなります。

prisma.schema
model Sample {
  id     Int    @id
  count  Int    @default(0)  
}

アプリケーション側で条件付きロジックで計算

集計処理が実際に行われるまでは null を許容したいなどの要件がある場合は、アプリケーションのロジックで null を適切に扱うような設計が必要になります。
特定のフィールドに対して加算操作を行う前に、そのフィールドが null であるかどうかを確認し、必要に応じてincrimentするか直接値を設定するかを算出します。

sample.ts
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 を使うかどうかなどについても考慮しておく必要がありそうです。

おわりに

内容としてはシンプルでわざわざ書くようなことではなかったかもしれないですが、初学者の方や将来の自分が忘れて同じミスをしてしまうこともあると思い備忘録として執筆しました。
本記事が今後の自分を含め誰かのサポートになると幸いです。

ファンタラクティブテックブログ

Discussion