prisma.io
Prisma Client API reference - prisma.io
prisma/prisma - github.com
- TypeScript ベース x GraphQL ライクな schema 駆動の ORM ツール
- schema 定義から TypeScript 向けの型定義を自動生成してくれる
- GraphQL サーバ内の ORM に使ったり、Next.js などの JS フレームワークから DB クライアントとして使ったり
- Seeding, Migration なども有しており JAMStack 開発の RDB まわりは一通りこいつに任せていい
- Roll back the latest migration group #4617
- migration で rollback に対応してない、理屈は ↓
- 「本番では安易に rollback せんで新しく migrate するやろ?」
- 「開発ではてめえで変更を戻して reset すればええやろ?」
- 基本、開発環境では migrate reset し、本番では migrate deploy で対応
- migration で rollback に対応してない、理屈は ↓
- Configuration with reverse proxy in custom path not possible #403
- (prisma studio の話) nginx で reverse proxy みたいな構成しづらいぽい
- Prisma Model Hooks #3547
- カスタム hook みたいなのは一応 Middleware として提供されているが、結構低レベル
- そのうち model 毎の hook とか追加されそう
Next.js から Prisma ORM を利用する Next.js + Prisma + NextAuth.js + React Query で作るフルスタックアプリケーションの新時代
VSCode 開発なら Syntax Highlight のため先に Prisma 拡張 入れておくとよい。
$ npm i -D prisma # 旧 @prisma/cli で init, migrate やらやるやつ
$ npm i @prisma/client # こっちが実際に ORM として使う DB クライアント
# 構成ファイルの作成
#
# prisma/ スキーマの定義場所
# .env DATABASE_URL 環境変数セット用、消して実行環境から入れてもいいはず
# postgres の場合は ?schema=public まで入れないと migrate は通っても
# ランタイムで Can't reach database server at で繋がらないことあった
#
$ npx prisma init
# schema の編集
#
$ vi prisma/schema.prisma
# DB migration (for dev)
# (schema 変更の sql 吐き出しと開発 DB への取り込み)
# 多分中で ↓ の prisma generate も一緒にやってる
#
$ npx prisma migrate dev --name first_migrate
# DB client ファイルの生成 (for prod)
# (schema 変更の @prisma/client へ向けた取り込み)
#
$ npx prisma generate
# DB migration (for prod)
# ↑ で吐き出し済みの sql を本番 DB へ流すだけのやつ
#
$ npx prisma migrate deploy
# Adminer みたいな DB の中身みる GUI 起動
#
$ npx prisma studio # localhost:5555 で展開される一般的な ORM と違い @prisma/client は schema.prisma に応じた DB client ファイルを prisma generate で都度生成し、それを import していることに注意。
- client の実体は
node_modules/.prisma/clientに保存される - 別 dir へ指定もできるけど git 管理は非推奨みたい
- あと
@prisma/clientのnpm i時には自動生成されるらしい- ややこしい ... どうせ必要なら必ず叩くことにしてくれ
schema.prisma から CLI (prisma) で sql と client js を生成して @prisma/client で触るって感じなのかな。
で ... こういう特性を持っているため ↓ みたいなよくある最初に package.json だけ COPY する感じの Dockerfile だと npm i 時に prisma/schema.prisma を参照できなくて prisma generate を内部で実行できず、結果 Error: @prisma/client did not initialize yet となって DB 接続できないので注意。
FROM node:14.15.4
EXPOSE 80
WORKDIR /app
# npm install の build step を全体 copy と分けて
# cache し build 時間を短縮させるよくあるやつ
#
# https://qiita.com/Mayumi_Pythonista/items/3bec2d15980c99503793
#
COPY package*.json ./
# schema.prisma がないので prisma generate されない
RUN npm ci
COPY . ./
# ここで生成してやる必要がある
RUN npx prisma generate
# ↑ で client 生成してからじゃないと build 通らん
# (node_modules にモノがないので)
RUN npm run build
CMD ["npm", "start"]あと new PrismaClient() のたびに db connection 発生するらしい (未検証) ので、インスタンス使い回す感じにするのがいいらしい。
warn(prisma-client) Already 10 Prisma Clients are actively runningの対処
Best practice for instantiating Prisma Client with Next.js
// libs/prisma.ts
/* eslint-disable no-unused-vars */
/* eslint-disable no-var */
// https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/nextjs-prisma-client-dev-practices
import { PrismaClient } from '@prisma/client'
import { createUserExtends } from 'usecase/extends/create-user-extends'
const prismaClientSingleton = () => {
return new PrismaClient({ log: ['info', 'warn', 'error'] })
.$extends(createUserExtends)
}
declare global {
var prismaGlobal: undefined | ReturnType<typeof prismaClientSingleton>
}
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
export { prisma }
if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prismaimport { prisma } from 'libs/prisma'
const users = prisma.user.findMany()補足として import のパス解決で相対パスにしたくない場合は ↓ のように構成してやればいい。
prisma 向け REPL (Read Eval Print Loop) ライブラリ、いわゆる GOD 。ただ seeding の項目で書いた ts-node x module:nodenext の組み合わせでそのうちいらなくなる気がする。
$ npm i -D prisma-repl
$ npx prisma-repl
> await db.user.count()
# Override database_url
$ npx prisma-repl --url postgres://xxx#
# よくある migration 関連コマンド
#
$ npx prisma migrate status # マイグレーション状態の確認
$ npx prisma migrate reset # 開発専用、全消し => 再構築 => seeding
# 開発環境専用のコマンド
# マイグレーションファイルの作成 => マイグレーション実行までやる
# prisma/schema.prisma 変更検知して prisma/migrations/ の
# 配下に 20210606142525_hoge.sql みたいなの生成してるぽい
#
$ npx prisma migrate dev --name ${MIGRATION_NAME}
# 本番環境専用のコマンド
# マイグレーションファイルの存在確認 => マイグレーション実行だけやる
# migrate dev で生成した .sql を DB に適用する本番専用のやつ
#
$ npx prisma migrate deployあんまちゃんと理解してないけど ... Prisma Migrate は migrate dev や migrate reset など開発時の DB 操作コマンドで tmp な DB を作る。
ので、開発環境で利用する DB では CREATE DATABASE などの権限を持つユーザで DB に接続しないといけない。
開発環境で利用する DB において (Heroku PostgreSQL を開発用に使うなどしてて) 上記権限ユーザを持てない場合は ↓ のように SHADOW_DATABASE_URL 環境変数セットするなどしてもう 1 個同じ構成の DB を用意しろ、ということらしい。
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL") // これ
}開発環境で Docker なんかで PostgreSQL コンテナ立ててつないでたりして、DB 全体の権限持ちユーザが使えるならいらない。
あと本番環境では migrate dev や migrate reset は使わないので、特に気にせんでよい。
- TypeStrong/ts-node ちゃんで
.tsファイルに書いた prisma script を実行するだけ - 「中で upsert なり組めばそれがもう seeding やろ?」って感じ、潔い ...
package.json > prisma.seedにts-node prisma/seed.tsとかかけばnpx prisma db seedでそれ実行したるっていうよくわからん配慮- 中で project の依存解決したいなら ts-node と dividab/tsconfig-paths セットで使うのがよき
prisma migrate reset
$ npm install -D typescript ts-node @types/node tsconfig-paths// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".", // import パス解決
"paths": { /* */ },
},
//
// ts-node 用の設定を追加
// https://github.com/TypeStrong/ts-node#via-tsconfigjson-recommended
//
"ts-node": {
//
// ts-node からの実行時に baseUrl, paths とか解決してもらう
// https://github.com/TypeStrong/ts-node#paths-and-baseurl
//
"require": ["tsconfig-paths/register"],
"compilerOptions": {
//
// module は NodeNext とかにしとくと import とかいい感じにしてくれる
// このあたり死ぬほどややこしいので ↓ ざっと読むとよき
// https://zenn.dev/teppeis/articles/2021-10-typescript-45-esm
//
"module": "NodeNext",
//
// 実行環境の node.js ちゃんが読める形に落とす
// ts-node の top level await 対応も考えると
// node.js v14.15.x とあわせてこの辺がいいのでは
//
"target": "es2018",
}
},
// ...
}// package.json
{
// これを set すると prisma migrate reset または
// prisma migrate dev 時に db rest とともに
// 自動実行されるらしい (開発環境用 CI ってコト!?)
//
// 開発中に今回の migrate dev で開発用の initialize
// 用の seed が壊れてねえよなぁ!? ってことが検知できる利点ある
//
// 本番環境での実行は原則行わないような立て付けなんだと思う
//
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
}// prisma/seed.ts
import { Prisma, PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
const alice: Prisma.UserCreateInput = {
email: '[email protected]',
name: 'Alice',
}
await prisma.user.upsert({
where: { email: alice.email },
update: data, // insert only なら update: {} とか
create: data,
})
}
main()
.catch((e) => {
console.error(e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})# ts-node で ↑ prisma/seed.ts 実行
$ npx prisma db seedmemory の少ない本番環境では ts-node だと killed されたりする。そんなときは tsx 使うほうがいい。
$ npx tsx prisma/some-seeds.tsHow to update many related records? #7547
Provide upsertMany operation in the client API #4134
seeding は prod において冪等性を捨てたほうがいいかも。
upsertMany みたいなものはまだない。ので、冪等な seeding を組みたいなら結構考えて独自に組まないといけない。(というか prisma の seed 機能は現状 data 操作の batch file でしかない)
そもそも現行 DB と seed file の diff をとって冪等に ... というアプローチ自体が、そこそこ大きなデータセットで破綻してしまう気がする (CI のたびに大量の Bulk update ... とかどの程度耐えられるのか) 。
とはいえ、殆ど変更されない & 変更に大きなコストが伴うような少量のマスターデータで、外部キー制約などの利点を生かさずとも管理しきれるものなら定数で管理する方向もある。またはファイルに逃がして stream する、別サービスとして API 連携する ... など。しかし、定数はオンメモリなので大量データに向かない、ファイルは I/O 負荷懸念、API は通信コストや異常系懸念などそれぞれ一長一短あり、どうしても DB に持たせたいケースは出てくる。
上記考慮し seeding は開発環境のみ自動実行 (CI) 前提 として、本番環境では冪等性のある自動実行は行わなず、手動実行する時系列ベースの seed file を運用する のが良さそうというのが、一旦の持論。
// package.json
{
// prisma migrate reset 後に最低限の seed を insert だけする seed
// prisma db seed コマンドは基本 CLI で自分で叩かない想定
//
"prisma": {
"seed": "ts-node prisma/local-seed.ts"
//
// これを set すると prisma migrate reset または
// prisma migrate dev 時に db rest とともに
// 自動実行されるらしい (開発環境用 CI ってコト!?)
//
// 開発中に今回の migrate dev で開発用の initialize
// 用の seed が壊れてねえよなぁ!? ってことが検知できる利点ある
//
},
"scripts": {
//
// ↑ のやつ、明示的 dev 環境で CLI で叩くならっていうやつ
//
"seed:dev": "prisma db seed",
//
// prod (本番) 環境で batch 的に手動実行する seed
// npm run seed:prod --file=hoge.ts みたいな
//
"seed:prod": "ts-node prisma/seeds/$npm_config_file"
}
}// 多分こんな感じ
prisma/
|
├ migrations/
| └ 20210101_init/
|
├ seeds/
| ├ 20220101-add-admins.ts
| ├ 20220201-add-prefectures.ts
| ├ 20220301-adhoc-update-admin-users.ts
| └ 20220401-update-prefecture-18.ts
| //
| // ↑ では必ず $transaction を張った中で
| // (create|update|delete)(Many)? で構成し
| // upsert などによる冪等性の担保は「やらない」
| //
| // 本番では CI に組み込まず batch 的に手動実行する
| // 実行済み seed の重複実行だけ (別途管理するなどして) 注意する
|
└ local-seed.ts
//
// migrate reset で走らせて ↑ seeds/ から
// adhoc-*.ts じゃないやつだけを抽出して
// 「ざっくり本番に近しい初期状態」にする seeder
//
// 多分そこそこ大規模な開発になると、こいつの中身は
// 本番 DB のマスターをマスク & データ量調整した
// ステージング DB からの dump & bulk insert になる
// prisma/seeds/20220401-update-prefecture-18.ts
import { prisma } from 'libs/prisma'
export async function main() {
//...
}
// 単体でも実行可能にしておく
main()
.catch((e) => {
console.error(e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})// prisma/local-seed.ts
import { prisma } from 'libs/prisma'
// local の db reset に必要なやつだけ import して順次実行
import { main as addAdmins } from 'prisma/seeds/20220101-add-admins'
import { main as addPrefectures } from 'prisma/seeds/20220201-add-prefectures'
import { main as updatePrefecture18 } from '20220401-update-prefecture-18'
async function main() {
await addAdmins()
await addPrefectures()
await updatePrefecture18()
}
main()
.catch((e) => {
console.error(e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})# 開発 (dev) 環境では migrate reset で
# drop DB & migrate & seeding やりなおし
# (prisma db seed 経由で local-seed.ts 実行)
#
$ npx prisma migrate reset
# 本番 prod) 環境ではファイル指定して batch 的に
# prisma/seeds/*.ts を CI による deploy 後に実行
#
$ npm run seed:prod --file=20220401-update-prefecture-18.tsConcepts / Components > Prisma schema
Reference / API reference > Prisma schema reference
npx prisma formatで自動整形してくれる- schema 変更後は migration の他に
prisma generateで client 生成・更新が必要npm iや開発環境でのprisma migrate devでは併せてやってくれる- Dockerfile などで「最初に
COPY package*.jsonRUN npm ciする」ようなケースではその後 schema ファイルの COPY してから改めて generate かけないといけないので注意
- client 生成・更新後は
prisma.user.count()のように client が使えるだけでなくUserCreateInputのような型の import も行える
datasource db {
url = env("DATABASE_URL")
provider = "postgresql"
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
email String @unique
name String?
role Role @default(USER)
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
published Boolean @default(false)
title String @db.VarChar(255)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
enum Role {
USER
ADMIN
}- DB 定義した Enum の type を生成できるので、プログラム側の制御は結構楽ちん
- 但し、選択した DB Engine やその version に強く依存することに注意
- 基本的に、削除はできないと思ったほうがいい
- あと postgres 12 以上じゃないと後から追加も鬼のように大変
// import して利用する場所によって type としても value としても使えるぽい
import { Role } from '@prisma/client'
const user = {
role: Role.USER // value - 'USER'
}
type UserProfile = {
role: Role // type - 'USER' | 'ADMIN'
}import { PrismaClient, Prisma } from '@prisma/client'
// user model の生成時の parameter を type 検証できる
const user: Prisma.UserCreateInput = {
email: '[email protected]',
name: 'Elsa Prisma',
posts: {
create: {
title: 'Include this post!',
},
},
}
const prisma = new PrismaClient()
const createUser = await prisma.user.create({ data: user })同じ model と複数の relation を貼りたいときは name をつけて従属側に専用の id attr を生やしてやる。
model User {
id Int @id @default(autoincrement())
name String?
writtenPosts Post[] @relation("WrittenPosts")
pinnedPost Post? @relation("PinnedPost")
}
model Post {
id Int @id @default(autoincrement())
title String?
author User @relation("WrittenPosts", fields: [authorId], references: [id])
authorId Int
pinnedBy User? @relation(name: "PinnedPost", fields: [pinnedById], references: [id])
pinnedById Int?
}前提、多用すべきじゃないが json field も対応してる。
generator client {
provider = "prisma-client-js"
previewFeatures = ["filterJson"] // preview 機能で filter も可能
}
model User {
// ...
pets Json?
}const json = [
{ name: 'Bob the dog' },
{ name: 'Claudine the cat' },
] as Prisma.JsonArray
// writing
const user = await prisma.user.create({
data: {
email: '[email protected]',
// js object から json への変換は自動でやってくれる
pets: json,
},
})
// reading
if (user?.pets && typeof user?.pets === 'object' && Array.isArray(user?.pets)) {
const petsObject = user?.pets as Prisma.JsonArray
const firstPet = petsObject[0] // { name: 'Bob the dog' }
}
// filtering (全体一致)
var json = { [{ name: 'Bob the dog' }, { name: 'Claudine the cat' }] }
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
equals: json,
},
},
})
// filtering (パス指定 & 値チェック)
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['petType'], // ['pet2', 'petName'] とか階層表現したり
string_contains: 'cat', // array_contains: ['Claudine'] とかもある
},
},
})↓ のように「 include で relation を保持している entity の型」が生成できる。
type UserWithPosts = Prisma.UserGetPayload<{
include: {
posts: true
}
}>Prisma.validator
Operating against partial structures of your model types
Mapped Types - typescriptbook.jp
- 定義済みの schema から型バリデーションを生成できる
User & { posts: Post[] }みたいなリレーション含めた型定義を動的に組むことも可能- 必ずワンセットで DB から引っ張るような model では特に便利
import { Prisma } from '@prisma/client'
// validator を使って型バリデーション object を生成
// こいつは find() とかの入力値に satisfies とかして型補完・型エラー検出させるためのもの
export const userWithPosts = Prisma.validator<Prisma.UserArgs>()({
include: { posts: true },
// ここで ↓ みたいに required field を指定したり、省くべきやつ指定したりも
select: {
email: true,
name: true,
encrypted_password: false,
posts: { title: true, body: true, published: true }
},
})
// ↑ の User & { posts: Post[] } みたいな select, relation 定義から型を生成
// こいつは DB 返却に satisfies とか、backend コードで利用する感じのはず
export type UserWithPosts = Prisma.UserGetPayload<typeof userWithPosts>使い回さないような定義なら ↑ より、prisma の query や mutation 関数書いた後にこいつで export するほうが楽。
async function getUsersWithPosts() {
const users = await prisma.user.findMany({ include: { posts: true } })
return users
}
export type UsersWithPosts = Prisma.PromiseReturnType<typeof getUsersWithPosts>↑ とあわせて Date 型などを override をした serialized modal 型を作ると api 連携しやすい。
/**
* ↑ に更に mapped types utility で再帰的に Date => string cast した
* 「 api 返却は posts 持ち user & 日付系ぜんぶ seriarize されてるやつやで」型を作る
*
* 理由
* ===
* next の api で res.status(200).json() とかしたとき iso string になる
* また getServerSideProps から渡すときも Date 型は super-json とか使わないと
* error になるので素直に back => front 時には JSON.stringify() してやり
* front-end では back-end から受け取ったデータをこの型で解決して引き回したいから
*/
export type UserWithPostsSd = WithSerializedDates<UserWithPosts>
// ↓ 再帰的に date => string cast する util
/**
* Utility Type of Serialized Prisma Model.
* (Date => string)
*
* @link https://github.com/prisma/prisma/discussions/5522#discussioncomment-4630647
*/
export type WithSerializedDates<Type> = {
[Key in keyof Type]: Type[Key] extends Date
? string
: Type[Key] extends Date | null
? string | null
: Type[Key] extends Date | undefined
? string | undefined
: Type[Key] extends Date | null | undefined
? string | null | undefined
: Type[Key] extends Record<PropertyKey, unknown>
? WithSerializedDates<Type[Key]>
: Type[Key] extends Record<PropertyKey, unknown> | null
? WithSerializedDates<Type[Key]> | null
: Type[Key] extends Record<PropertyKey, unknown>[]
? WithSerializedDates<Type[Key][number]>[]
: Type[Key]
}
export const castSerializable = <T>(data: T) => (
JSON.parse(JSON.stringify(data)) as WithSerializedDates<T>
)// front ではきっとこう使う
import type { UserWithPostsSd } from 'path/to/type'
const res = await fetch('/api/users')
const user: UserWithPostsSd = await res.json()
console.log(
user.posts[0].published // ISO string format
)Concepts / Components / Prisma Client > Custom Validation
Add runtime validation to models #3528
prisma は schema に対する type validation しか責務として持っていない。よって user input に対する runtime validation は手前で好きな lib 使って対処しろとのこと。
ざっと調べた感じ zod の schema を schema.prisma から自動生成する zod-prisma の組み合わせが楽そう。
$ npm i zod
$ npm i -D zod-prisma// schema.prisma
// ...
generator zod {
provider = "zod-prisma"
// 生成する zod schema の出力先
// 利用時に import するので tsconfig.json で path 解決するなり
// next.config.js で alias 指定するなりしてあげるといい
//
output = "./libs/zod"
}
model Post {
// とくに何も指定しなければ prisma を解析して
// zod schema を自動生成してくれる
//
// ↓ なら id: z.string().uuid() とかになる
//
id String @id @default(uuid())
// こんな風に @zod rich comment で詳細指定もできる
//
contents String /// @zod.max(10240)
//
// 上に書いてもよき
//
/// @zod.max(255, { message: "The title must be shorter than 256 characters" })
title String
}# ↑ schema の変更後に prisma generate する
$ npx prisma generate// ↑ の prisma generate でこんな zod schema が
// output で指定した dir へ生成される
//
// こいつを .gitignore するか ... それとも登録するか悩ましいが
// 全部を schema.prisma に書ききる & パターンを統一させるのはしんどいので
// 基本的には zod-prisma は boilerplate 生成ツールとして割り切って
// この zod schema 定義をガリガリ更新していくほうがいいかなぁって印象
//
export const PostModel = z.object({
id: z.string().uuid(),
contents: z.string().max(10240),
title: z.string().max(255, { message: 'The title must be shorter than 256 characters' }),
})// ↑ で生成した zod schema を import
import { UserModel } from 'libs/zod' // path は適当に解決してね
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
try {
// zod schema で入力値を validation してやる
const data = UserModel.parse({ /* おそとからのにゅうりょく */ })
// ↑ で ZodError にならなければ生成
// このとき data は既に typed な状態になってるらしい
//
const createdUser = await prisma.user.create({ data })
} catch(e) {
// .parse() の検証に失敗すると ZodError になるので
// あとは catch した例外処理でお好きに異常系を組んでやる
//
console.error(err)
/*
ZodError の中身はこんな感じ、この一連の処理を formik やら
react-final-form やらの validate function として登録、
このエラーを wrap して返却して ... みたいなことする
[
{
"code": "invalid_type", // エラータイプ
"expected": "string", // 期待した型
"received": "number", // 受け取った値の型
"path": [ "name" ], // エラーが発生したプロパティへのパス
"message": "Expected string, received number" // エラー内容
}
]
*/
}イメージこんな。
import { Form } from 'react-final-form'
<Form
validate={(values) => {
try {
UserModel.parse(values)
} catch (e) {
return e.formErrors.fieldErrors
}
}}
></Form>毎回 try catch 書くのだるいなら wrapper かませとのこと。
import { z } from 'zod'
export const validator = <T extends z.ZodType<any,any>>(schema:T)=>(values:any)=>{
try {
schema.parse(values)
return {}
} catch (e) {
return (e as z.ZodError).formErrors.fieldErrors
}
}import { validator } from 'libs/zod'
import { UserModel } from 'libs/zod'
import { Form } from 'react-final-form'
<Form validate={validator(UserModel)} />Concepts / Components / Prisma Client > CRUD
Concepts / Components / Prisma Client > Relation Queries - リレーション系はこちら
schema.prisma で @id や @unique など一意性の定義をしたものを指定して検索。
rejectOnNotFound option を有効化しないと、存在しない場合に exception したりせずに null を返す挙動。
// By unique identifier
const user = await prisma.user.findUnique({
where: {
email: '[email protected]',
},
})
// By ID
const user = await prisma.user.findUnique({
where: {
id: 99,
},
rejectOnNotFound: true, // こいつ指定すれば not found 時に error になる
})
// By multiple key (@@id or @@unique)
//
// model TimePeriod {
// year Int
// quarter Int
// total Decimal
//
// @@id([year, quarter]) こいつ
// }
//
const timePeriod = await prisma.timePeriod.findUnique({
where: {
year_quarter: { // こんな一意性の指定ができる
quarter: 4,
year: 2020,
},
},
})
// with SELECT
const user = await prisma.user.findUnique({
where: {
email: '[email protected]',
},
select: {
email: true,
name: true,
},
})こちらは一意じゃなくて OK な「最初のやつ返す」系。存在しない場合は default で null 返却。
findUnique 同様に rejectOnNotFound 指定で NotFoundError 吐かせられる。
const user = await prisma.user.findFirst({
where: { name: 'Alice' },
rejectOnNotFound: true, // 存在しない場合は NotFoundError
})
// OR, AND (NOT もあるよ)
const users = await prisma.user.findFirst({
where: {
OR: [
{
name: {
startsWith: 'E',
},
},
{
AND: {
profileViews: {
gt: 0,
},
role: {
equals: 'ADMIN',
},
},
},
],
},
})
// WHERE by relations
const users = await prisma.user.findFirst({
where: {
email: {
endsWith: 'example.com'
},
posts: {
some: {
published: false
}
}
},
}// SELECT * FROM users
const allUsers = await prisma.user.findMany()
// LEFT JOIN
const allUsers = await prisma.user.findMany({
include: { posts: true },
})
// WHERE OR
const filteredPosts = await prisma.post.findMany({
where: {
OR: [
{ title: { contains: 'prisma' } },
{ content: { contains: 'prisma' } },
],
},
})// INSERT
const user = await prisma.user.create({
data: {
name: 'bob',
email: '[email protected]',
posts: {
create: { title: 'bob\'s first post' },
},
},
})多分 Bluk Insert 扱い、今んとこ relation の同時生成はサポート外。
const createdCount = await prisma.user.createMany({
data: [
{ name: 'Bob', email: '[email protected]' },
{ name: 'Bobo', email: '[email protected]' }, // Duplicate unique key!
{ name: 'Yewande', email: '[email protected]' },
{ name: 'Angelique', email: '[email protected]' },
],
skipDuplicates: true, // Skip 'Bobo'
})// UPDATE
const post = await prisma.post.update({
where: { id: 42 },
data: { published: true },
})const upsertUser = await prisma.user.upsert({
where: {
email: '[email protected]',
},
update: {
name: 'Viola the Magnificent',
},
create: {
email: '[email protected]',
name: 'Viola the Magnificent',
},
})単一削除、1 件以上あるとエラー。
// DELETE FROM xxx WHERE id = xxx
const deleteUser = await prisma.user.delete({
where: {
email: '[email protected]',
},
})複数削除ならこっち。
// DELETE FROM xxx WHERE xxx
const deleteUsers = await prisma.user.deleteMany({
where: {
email: {
contains: 'prisma.io',
},
},
})原則、find 系の query の返却 object は ↓ の状態になっている。
- model の全 field を含む
- relation を含まない
自身あるいは関連 relation の field 絞り込みは select で行い、relation の field 絞り込みを行わずに全 field 取得する際は後述の include を使う、という立て付け。
内部的には全体的に LEFT OUTER JOIN を使ってるみたいで、INNER JOIN するには工夫が要る気がする。
const users = await prisma.user.findMany({
select: {
name: true,
posts: {
// posts の left outer join
select: {
title: true,
},
},
},
})
// {
// "name": "Sabelle",
// "posts": [
// {
// "title":"Getting started with Azure Functions"
// },
// {
// "title":"All about databases"
// }
// ]
// }↑ のような select による relation の nest は 親 model に対して select をかける ときしか使えないことに注意。
const getUser = await prisma.user.findUnique({
where: {
id: 1,
},
// select: { name: true } <-- NG
// こんな感じで include と併せて個別に select 指定とかはできません
include: {
posts: {
select: {
title: true,
},
},
},
})まるっと left join たのんますってやつ。prisma で関連 relation を取得する手法はだいたい ↓ 3 パターンで、この include を使うのが最も手軽。
- この include で指定する
- select の nest で指定する
- 後述の where + fluent api で後から別 query 叩く
const getUser = await prisma.user.findUnique({
where: { id: 19 },
include: { posts: true },
})
// nest するなら
const user = await prisma.user.findMany({
include: {
posts: {
include: {
categories: true,
},
},
},
})
// where で join 条件とか
// left なので条件一致な relation が「あれば一緒に」取得
// なくても親 model は取得する (rails だと inner join メインなので混乱する...)
const result = await prisma.user.findMany({
include: {
posts: {
where: { published: true },
},
},
})// OR や NOT (普通に条件追加すると AND 扱い)
const result = await prisma.user.findMany({
where: {
OR: [
{ email: { endsWith: 'prisma.io' } },
{ email: { endsWith: 'gmail.com' } },
],
NOT: {
email: { endsWith: 'hotmail.com' },
},
},
})
// relation field による絞り込み
const result = await prisma.post.findMany({
where: {
published: false,
user: {
email: { contains: 'prisma.io' },
},
},
})
// gt lt とか
const result = await prisma.user.findMany({
where: {
posts: {
some: {
views: {
gt: 10,
},
},
},
},
})カラムに対する IN や NOT gt, lt とかの filter operation はこんな感じで指定。
const result = await prisma.user.findMany({
where: {
id: {
in: [22, 91, 14, 2, 5], // in
notIn: [23, 92, 15, 3, 6] // not in
},
name: { not: 'Eleanor' }, // not
email: {
contains: 'test', // contains (for string)
endsWith: '@example.com', // endsWith (startsWith)
},
stars: { lt: 9 }, // lt (lte, gt, gte)
},
})prisma ちゃんは left join メインなので inner join 的な「持ってないやつは結果から省く」が言いづらい。
relation の count による filtering は未対応 だが、一応「少なくとも 1 件あるやつだけ来い」は where と some で言える。多分 inner join だと思う。
// 少なくとも 1 つの posts を持つ users カモン
const usersWithSomePosts = await prisma.user.findMany({
where: {
posts: { some: {} },
},
})
// 1 つも posts 持ってない users カモン
const usersWithSomePosts = await prisma.user.findMany({
where: {
posts: { none: {} },
},
})Is there any way to filter by whether a relation exists or not? #2772
prisma ちゃんは (略) 1 対 1 の「持ってない」も表現しづらい。
上記 none / some は hasMany (one-to-many) 関係でしか使えない ため **hasOne (one-to-one) 関係では is null で表現してやる。
// OAuth とかで認証してて Account を持っている users カモン
const usersHasAccount = await prisma.user.findMany({
where: {
account: { isNot: null },
},
})
// Email とかで認証してて Account 持ってない users カモン
const usersHasNoAccount = await prisma.user.findMany({
where: {
account: { is: null },
},
})prisma ちゃんは (略)「特定条件の relation を持っているやつ、その relation と一緒にカモン」が言いづらい。
include の where と親 model の where は独立しているので、このケースの場合は以下のように親 model 側と relation 側に同じ意味の where 条件を食わせてやるらしい。
// - published post 保持 user だけ post と一緒にほしい
// - include where だけだと published post のない user も一緒に来ちゃう
// - だったら user に published post もってるやつだけカモンって言っちゃう
//
const result = await prisma.user.findMany({
where: {
posts: {
some: { published: true },
},
},
include: {
posts: {
where: { published: true },
},
},
})where で絞り込んだ結果から relation 集合を持ってくる、逆参照的な感じ。
// user 絞り込んで、そいつらの posts カモン
const postsByUser: Post[] = await prisma.user
.findUnique({ where: { email: '[email protected]' } })
.posts()
// ↑ と等価、こちらの方が 1 SQL で済む利点がある
// ただ graphql 的にはこっちのが batching が効いていいらしい
const postsByUser = await prisma.post.findMany({
where: { author: { email: '[email protected]' } },
})const results = await prisma.post.findMany({
skip: 30,
take: 10,
where: {
email: {
contains: 'Prisma',
},
},
orderBy: {
title: 'desc',
},
})generator client {
provider = "prisma-client-js"
previewFeatures = ["fullTextSearch"]
}// All posts that contain the words 'cat' or 'dog'.
const result = await prisma.posts.findMany({
where: {
body: {
search: 'cat | dog',
},
},
})
// All drafts that contain the words 'cat' and 'dog'.
const result = await prisma.posts.findMany({
where: {
status: 'Draft',
body: {
search: 'cat & dog',
},
},
})Aggregation, grouping, and summarizing
aggregate - api-reference
_avg_sum_min_max_countなどで field 指定_count以外は null 値許容_countは 0 返却、それ以外は集計除外 + 全て null なら null 返却
- 返却 object は
._avg.fieldみたいな感じで集計結果にアクセス可能
// 全女性 user のうち若さ上位 10 名の年齢平均が知りたい
const aggregations = await prisma.user.aggregate({
_avg: { age: true }, // _sum, _min, _max, _count 利用可能
where: { gender: 'FEMALE' },
orderBy: { age: 'asc' },
take: 10,
})
console.log('Average age:' + aggregations._avg.age)// user の posts 数カモン
const usersWithCount = await prisma.user.findMany({
include: {
_count: { select: { posts: true } },
},
})
// { id: 1, _count: { posts: 3 } },
// { id: 2, _count: { posts: 0 } },
// { id: 3, _count: { posts: 2 } },
// count + _all でレコード件数と null field 件数の比較とか
const userCount = await prisma.user.count({
select: {
_all: true, // Count all records
id: true, // Count all non-null field values
},
})
// { _all: 30, name: 10 }// スウェーデンを除く user の profile view 数を country 別で集計
// 但し profile view 数 100 以上の user に限る
const groupUsers = await prisma.user.groupBy({
by: ['country'],
where: { country: { notIn: ['Sweden', 'Ghana'] } },
_sum: { profileViews: true },
having: {
profileViews: { _avg: { gt: 100 } },
},
})const result = await prisma.user.findMany({
where: {},
distinct: ['name'],
})// relation つき
const posts = await prisma.post.findMany({
orderBy: {
author: { name: 'asc' },
},
})
// count 結果で
const getActiveusers = await prisma.user.findMany({
orderBy: {
posts: { count: 'desc' },
},
})
// multiple order
const users = await prisma.user.findMany({
select: {
email: true,
role: true,
},
orderBy: [
{ email: 'desc' },
{ role: 'desc' },
],
})
// postgres v3.5 以上の full text search で関連語句検索的な
const posts = await prisma.post.findMany({
orderBy: {
_relevance: {
fields: ['title'],
search: 'database',
sort: 'asc'
},
})import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function transfer(from: string, to: string, amount: number) {
return await prisma.$transaction(async (tx) => {
// 1. Decrement amount from the sender.
const sender = await tx.account.update({
data: { balance: { decrement: amount } },
where: { email: from },
})
// 2. Verify that the sender's balance didn't go below zero.
if (sender.balance < 0) {
throw new Error(`${from} doesn't have enough to send ${amount}`)
}
// 3. Increment the recipient's balance by amount
const recipient = tx.account.update({
data: { balance: { increment: amount } },
where: { email: to },
})
return recipient
})
}
async function main() {
// This transfer is successful
await transfer('[email protected]', '[email protected]', 100)
// This transfer fails because Alice doesn't have enough funds in her account
await transfer('[email protected]', '[email protected]', 100)
}- prepared statement つきの生 sql を書きたいときのやつ
- generics で返り値の型指定もできる
- 集計系かどうかによらず、返り値は必ず
[]になってるので注意 - mutation 系は $executeRaw を使う
import { Prisma, User } from '@prisma/client'
const domain = 'example.com'
const users = await prisma.$queryRaw<User[]>(
Prisma.sql`SELECT * FROM User WHERE email like '%@${domain}'`
)import { Prisma } from '@prisma/client'
const ids = [1, 3, 5, 10, 20]
const result = await prisma.$queryRaw`SELECT * FROM User WHERE id IN (${Prisma.join(ids)})`PrismaClientインスタンスの生成後に$use()で hook を仕掛けられる- 生成箇所 1 つにしておいて export して全体で引き回すみたいな感じにしないといけなそう
- model 毎、とかはまだないので
ifで分岐作るしかない - とはいえ model みて fluentd とかログサーバに飛ばしたりとかはできそう
import { Prisma } from '@prisma/client'
export const createPostMiddleware: Prisma.Middleware = async (params, next) => {
if (params.model === 'Post' && params.action === 'create') {
params.args.data.language = params.args.data.user.language
}
return next(params)
}import { PrismaClient } from '@prisma/client'
import { createPostMiddleware } from './middlewares'
const prisma = new PrismaClient()
// middleware 登録
prisma.$use(createPostMiddleware)
export prisma // 全体でこいつを import して引き回すparams.args.data とかからの型ヒントなくなるうえに、usecase 毎にクエリ分けているなら「本当の意味で全体にかけたい」みたいなことって意外とない気がする。
ある動線からは「正」だけど、違う動線からは別の動きをしたいとか結構あるので、導入の前によく考える。本当に middleware としての使い方しかないんじゃないかな。
https://www.prisma.io/docs/orm/prisma-client/client-extensions
prisma/prisma#19725
いつのまにか middleware が非推奨になって extension でなんとかしろってことになっとる。
import { Prisma } from '@prisma/client'
export const createUserExtends = Prisma.defineExtension({
query: {
user: {
async create ({ args, query }) {
if (!args.data.color) {
args.data = {
...args.data,
color: 'orange',
}
}
return query(args)
},
}
}
})
export const prisma = new PrismaClient().$extends(createUserExtends)