Open9

prismaの気になりポイント

tkowtkow

prismaのdate型はdefaultではUTC以外のタイムゾーンでの保存をサポートしていない(DB依存型を使う場合はtimstampzなどを選択できるが)。
db.Date型(Postgres)はNodejsのDate型オブジェクトをvalueとしてわたす時、nodejsのシステムのタイムゾーン->UTCへ変換されるがこの時、Date型を使用しているとUTCの時刻が問答無用で00:00:00に切り捨てられる。
つまり、
NodejsのDateオブジェクトで日本時間における2022/01/02を表したいとした時にJSTで生成されたDateインスタンスは2022-01-01::15:00を保持しているが、そのままこれをDBのDate型のレコードに保存した場合15:00部分が切り捨てられるため、DBには2022-01-01が保存され、読み出したDateも2022-01-01::00:00:00::UTCとして読み出されてしまう。これによって集計テーブルを利用したい場合などで、集計する日付をそのまま指定してしまうと1日ずれた値が保存されてしまう。
これを修正するために、あえて日付をずらして保存する方法があるが、負の方向のオフセットがあるタイムゾーンでは、DBデータにDateをUTCで保存する場合、ローカルTimezoneで指定された日付と実際のクエリの指定日で引っかかる日付が1日分前にずれてしまうため、汎用的なロジックによって矯正するのは難しい。

(例えば UTCで保存されている2022-01-02::00:00:00は負の方向のオフセットがあるタイムゾーンでは2022-01-01::${負のオフセット時刻}となり、ローカルタイムゾーンから取得された日付をそのままレコードに入れると、1日前にずれてしまう。)

Date型を扱う場合はUTC以外を扱う場合はDate型を使用するのはやめた方がいいだろう。timestampz型かInt型を使い、リージョン毎にDBを分けて扱うのがよいと思われる。

また、Date型の値には複合ユニークインデックスを作成するとwhereのunique条件の型が生成され、DB上のインサート時にも複合ユニークインデックス内で重複した値をインサートしようとするとエラーになるが、upsertの実行時に重複するwhere条件のcreateをupdateに切り替えできないバグがある(prismaなのかpostgresql由来なのかは不明)。
prismaのupsertは特定の条件下でcreateの重複条件に引っかかり正しくupdateに移行できないケースのバグが何点かイシュー上でも報告されているので注意が必要。

tkowtkow

prismaのslack communityに投稿した内容。rollback戦略は気を遣う。
SHADOW_DATABASE_URLを使って、down migrationスクリプトを生成できる。
以下によって本番環境の適用前ならmigrationの書き換えが可能。

I realized rollback my dev database to point  I want to do so, and re-migrate.
For someone in same situations, I wrote what I did.
1. create database for shadow_database
2. remove migration files temporally you want to rollback
3. run `prisma migrate diff --from-schema-datamodel prisma/schema.prisma --to-migrations prisma/migrations --shadow-database-url "${SHADOW_DATABASE_URL}" --script > down.sql`
4. run `prisma db execute --file ./down.sql --schema prisma/schema.prisma`
5. delete records from _prisma_migrations you rollbacked
prisma migrate dev  detect conflict and intend to reset migration even if migrate records have marked as applied or rollbacked column, so you need delete migration records in development.
If you need to keep migration files for production already applied, restore remove migration files and applied.
prisma resolve (--rolled-back or --applied)

本番環境に適用してしまったファイルはdown scriptをマイグレーションファイルとして適用した後に、さらに新しいマイグレーションファイルを作り直すのが良さそう。このあたり、appliedやrolled backを多用すると事故る可能性が高い。

up downを両方定義するの機構は昔あったみたいだが、downスクリプトは結局手動で管理すると事故りやすいので今は撤廃されているようだ。

down scriptの作成に新たにDBを配置しておいたり、migrationを消して生成する必要があるなど、少し手間感が否めないがdown migrationを自動生成する機能が存在していたのは朗報。

参考
https://www.prisma.io/docs/guides/database/developing-with-prisma-migrate/generating-down-migrations
https://www.prisma.io/docs/guides/database/production-troubleshooting#fixing-failed-migrations-with-migrate-diff-and-db-execute

tkowtkow

migrationを適用する前なら新しく書き直したschemaファイルとmigation適用前のDBの状態からdown scriptをあらかじめ生成しておくこともできる。しかし、基本的にはマイグレーションをロールバックしたいという願いは何かの過ちを犯してしまった場合に生じると思うので、大体、SHADOW_DATABASEを使う事がほとんどだと思われる。これのためにローカルにcreate databaseを実行する手間が出ることは仕方ないと割り切りましょう。本番に余計な設定を反映したくない方はlocalでshadow database用のcreate databaseを用意してオプション引数からDBURLを指定するとprismaファイルを汚さずともdown migration scriptの作成ができます。

tkowtkow

@prisma/clientをimportすると自動的にenvファイルをロードしてしまうらしい。この仕様はどうなんだろう。.envファイルを複数利用している場合、順番によっては意図せず環境変数がオーバーライドされて思わぬバグを踏みそう(というか、踏んだ。環境変数を設定しない、dotenvも設定しないのになぜか.envの環境変数が読み込まれてて、嫌だなぁ。怖いなぁ。なんて考えていると@prisma/clientが一番最初にimportされており)

tkowtkow

動的クエリの書き方

slackコミュニティに投稿したやつを流れないようにここにも書く

https://www.prisma.io/docs/concepts/components/prisma-client/raw-database-access#parameterized-queries

function someQuery (where: string[], ...parameters: QueryParameter[]) {
  return prisma.$queryRawUnsafe(`select
    ra.company_id as company_id,
    ra.user_id as user_id,
  from companies as cs
  join users as us  on cs.id = us.company_id
  where 
    ${where.join(' \r\n')}
  ;`, ...parameters)
}

Our input should be
someQuery(['company.id = $1', 'and', 'üser.id = $2'], ...[varCompanyId, userId])

function someQuery (companyId: number | string, where: string[],  ...parameters: QueryParameters[]) {
  return prisma.$queryRawUnsafe(`select
    ra.company_id as company_id,
    ra.user_id as user_id,
  from companies as cs
  join users as us  on cs.id = us.company_id
  where 
    company.id = ${parameters.length + 1}
    ${where.join(' \r\n')}
  ;`
  , ...parameters, companyId,
)
}

queryInstance.where([‘comanyId’, ‘=’, companyId]) and append companyId = 1 to raw string, and paramerters.push(companyId), inner state and final result input prisma.queryRawUnsafe
like another orm but we should use prisma api if easily write query. My solution seems to be over hack.

tkowtkow

prismaはポリモーフィック関連に対応していない。
https://github.com/prisma/prisma/issues/1644
個人的には基底テーブルを使うデザインパターンの方が重宝するので新規プロジェクトを作る場合には問題にならないが、既存のアプリケーションでpolymorphic関連を使ってしまっている場合、移行がかなり難しくなりそうだ。

tkowtkow

prismaはimport時にdotenvを勝手にloadしてしまうこの仕様は非常に注意すべきで、.envの値で意図していない変数を読み込んでしてしまうことがある。

公式にはdotenv-cliを経由して実行してenvファイルを切り替えられるよとあるが、これは事情を知らない開発者や普段プロジェクトを切り分けてjestをmanualで走らせる癖を持ってる人がjestを直で走らせてしまった時に事故るのでfool proofとしてやや弱い。

このような自動でenvファイルを読み込むようなフレームワークは珍しくないが、オプトアウトしたりNODE_ENVが指定されている時にはdefaultのロードファイルをそちらに切り替えて運用できるようにしてほしいと切に感じる。

dotenvはload時に値がはいっている環境変数に関してはloadをスキップできる。
jestでのおすすめの方法はglobal setupで最初にprocess.env.DATABASE_URLなど必要なパラメータをglobal setupやenvで設定仕切ってしまうことだ。