0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Prismaで0日付が扱えないのでDrizzleに乗り換えた話

Posted at

TypeScript(NestJS)で新規プロダクトのバックエンドを開発する中で、既存のMySQLデータベースにORM(Object Relational Mapper)を導入しました。

最初はPrismaを採用していたのですが、既存DBの日付カラム(0日付)を扱えないという問題にぶつかり、最終的にDrizzleへ切り替えました。

この記事では、その経緯と背景を整理します。

ORMを採用した理由

新規プロダクトはTypeScript(NestJS)で構築しており、DBとアプリケーション間のデータを型安全に扱いたいというのが大きな目的でした。

PrismaのようなORMは、DBスキーマをもとにTypeScriptの型を自動生成してくれます。

たとえば、MySQLのDATETIMEカラムを持つテーブルをselectすると、アプリケーション側でも自動的にDate型として扱えるようになります。

手動で型変換を行う必要がないため、開発効率や保守性が格段に上がります。
自前で型変換するのは大変なので採用しました。

問題:PrismaではDatetime型の0日付を扱えない

ここで問題が発生しました。
Prismaでは、MySQLの0000-00-00 00:00:00という「0日付」をJavaScriptのDate型に変換できません。

MySQLではDATETIMEDATE型のデフォルト値を0000-00-00に設定できますが、PrismaがこれをパースするときにP2020エラーが発生します。

Prismaは0000のDateオブジェクトをJSのDateにパースするときに0日付はJSでパースできません。
下記のようなP2020エラーが発生します。

code: 'P2020', 
meta: { modelName: 'member', 
 details: 'The column created contained an invalid datetime value with either day or month set to zero.' }, 
clientVersion: '6.14.0'

公式でも「0000日付」はサポート外と明記されています。

image.png

既存DBでは、「期限なし」「未登録」などを表すために0日付を多用しておりました。
ログイン管理や期限管理などでDATETIMEカラムを扱う箇所が多く、非常に困りました。

試した対策

Prismaのスキーマ定義側で型をStringにしてみたり、dbgenerationを使って強制的に0日付をデフォルトにしてみたりと検証を行いました。

model member {
  mid         Int      @id @default(autoincrement())
  created     DateTime @default(dbgenerated("'0000-00-00 00:00:00'")) @db.DateTime(0)
  expire_time DateTime @default(dbgenerated("'0000-00-00 00:00:00'")) @db.DateTime(0)
}

あるいは、文字列として定義しても:

model member {
  mid         Int     @id @default(autoincrement())
  created     String  @db.VarChar(25)
  expire_time String  @db.VarChar(25)
}

結果は同じでした。
Prisma内部のRust製ドライバはMySQLの型情報をもとにDateTimeとして扱うため、型定義を変えても0日付を受け取った時点でパースエラーになります。

Prismaのアーキテクチャを理解する

ここで重要なのは、Prismaの内部構造です。

PrismaはNode.jsで動くように見えて、実際のDBとのやり取りはRustで実装された「Prisma Query Engine」が担っています。

image.png

+------------------+         +-------------------+         +----------------+
|  Node.js (JS)    | <-----> |  Prisma Query     | <-----> |  MySQL DB      |
|  └─ PrismaClient |         |  内蔵ドライバ(Rust)|         |  (TCP 接続)    |
+------------------+         +-------------------+         +----------------+

このエンジンにはMySQL用ドライバが内蔵されており、アプリケーション側からドライバの挙動を変更することはできません。

そのため、0000-00-00string扱いしたり、NULLに変換するような制御も不可能でした。

つまりいくらスキーマファイルで定義しようが、アプリケーション側から内蔵ドライバの操作を行うことはできません。

最近のバージョンだとより柔軟に対応させるためか、任意でデータベースドライバを組み合わせることができるようです。

ただ、Prismaを使うための制約が多く、検証していませんが、こちらは不採用にしました。

image.png

対策:Drizzleへ乗り換え

最終的に、Drizzle ORMへ切り替えました。

DDLを変更して既存DBを更新する選択肢もありましたが、既に稼働中のサービスだったため、影響範囲が大きすぎると判断しました。

もし新規DBを構築できる環境なら、ORMに合わせてDB設計を見直すのが一番早いです。

Drizzleの仕組みと柔軟性

Drizzleは、Prismaと違いRustエンジンを持たず、Node.js内で完結します。
内部では公式のmysql2ドライバを直接利用しており、アプリケーション側からオプションを制御できます

+-----------------------+           +-----------------------------+
|  Node.js (TypeScript) |   ---->   | MySQL / PostgreSQL / SQLite |
|  └─ Drizzle ORM       |           |   (Native DB over TCP)      |
|     ├─ drizzle-orm    |           +-----------------------------+
|     ├─ mysql2 / pg    | (JSドライバを直使用)
+-----------------------+

これにより、日付の扱いを文字列として受け取るように設定可能です。
mode: 'string'にすることで、ドライバが0000-00-00 00:00:00を文字列として安全に扱ってくれます。この柔軟性が決定打でした。

export const member = mysqlTable(
  'member',
  {
    memberId: int({ unsigned: true }).autoincrement().notNull(),
    created: datetime({ mode: 'string' })
      .default('0000-00-00 00:00:00')
      .notNull(),
    expireTime: datetime('expire_time', { mode: 'string' })
      .default('0000-00-00 00:00:00')
      .notNull(),
  },

まとめ: ドライバを制御できるかどうかが分岐点

今回のポイントです。

ORMがどのドライバを使い、アプリケーションから制御できるか。

ORM ドライバ 挙動制御 0日付の扱い
Prisma 内蔵(Rust) ❌ 固定 ❌ サポート外
Drizzle mysql2(JS) ✅ 自由に設定可能 ✅ 文字列で扱える

Prismaは強力で高機能ですが、既存DBを持つ環境では融通が利きにくい。
一方Drizzleは軽量かつ柔軟で、既存スキーマを壊さず導入できるのが大きな利点です。

新しいORMを導入するときは、DBスキーマとの互換性とドライバの制御範囲を確認すること。

既存DBがある場合、柔軟に設定できるDrizzleのほうが軍配が上がりそうです。

私としても勉強になりました!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?