はじめに
Prismaを使っていると少し複雑な検索を行う時、生のSQLを使用してViewを作成することがありますが、型安全ではなかったりちょっと不便です。
ちょっといい感じの方法があったのでメモします。
参考
前提条件・環境
- Prisma 4.16.0以上
- PostgreSQL(本記事の例)
- TypeScript環境
サンプルスキーマ
対象のテーブルはこんな感じ
// schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
profile Profile?
}
model Profile {
id Int @id @default(autoincrement())
bio String
avatar String?
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
実装手順
1. Preview機能の有効化
schema.prisma
のgeneratorブロックにpreviewFeatures = ["views"]を追加します
generator client {
provider = "prisma-client-js"
previewFeatures = ["views"]
}
2. SQLビューの作成
データベースに直接SQLを実行してViewを作成します。
CREATE VIEW user_info_view AS
SELECT
gen_random_uuid() AS id,
u.id AS user_id,
u.email,
u.name,
p.bio,
p.avatar,
CASE
WHEN p.id IS NOT NULL THEN true
ELSE false
END AS has_profile
FROM "User" u
LEFT JOIN "Profile" p ON u.id = p."userId"
ORDER BY u.id;
3. スキーマ定義の取得
以下のコマンドを実行して、データベースからスキーマ定義を取得します
npx prisma db pull
schema.prisma
に以下のような定義が自動追加されます:
view user_info_view {
id String? @db.Uuid
userId Int?
email String?
name String?
bio String?
avatar String?
hasProfile Boolean?
@@ignore
}
4. スキーマ定義の修正
自動生成された定義をPrisma Clientで使用できるように修正します。
view user_info_view {
- id String? @db.Uuid
+ id String @id @default(uuid()) @db.Uuid
userId Int?
email String?
name String?
bio String?
avatar String?
hasProfile Boolean?
}
5. マイグレーションファイルの作成
空のマイグレーションファイルを作成します
npx prisma migrate dev --create-only --name add_user_info_view
生成されたマイグレーションファイルに、Step 2で使用したSQL文を記述します。
-- CreateView
CREATE VIEW user_info_view AS
SELECT
gen_random_uuid() AS id,
u.id AS user_id,
u.email,
u.name,
p.bio,
p.avatar,
CASE
WHEN p.id IS NOT NULL THEN true
ELSE false
END AS has_profile
FROM "User" u
LEFT JOIN "Profile" p ON u.id = p."userId"
ORDER BY u.id;
動作確認
以下のコマンドを実行して、エラーが発生しないことを確認します。
npx prisma migrate dev
npx prisma generate
新たにマイグレーションファイルが作成されなければ、セットアップは正常に完了しています。
使用例
型安全にビューを操作できるようになります。
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// 全ユーザー情報を取得
const allUsers = await prisma.user_info_view.findMany();
// プロフィールがあるユーザーのみ取得
const usersWithProfile = await prisma.user_info_view.findMany({
where: {
hasProfile: true
}
});
// 特定のユーザーを検索
const userByEmail = await prisma.user_info_view.findFirst({
where: {
email: 'user@example.com'
}
});
// 型安全性の恩恵
usersWithProfile.forEach(user => {
console.log(user.email); // TypeScriptの型推論が効く
console.log(user.bio); // string | null として推論される
});
まとめ
もうちょっとスマートな方法ありそうだなぁとも思います。