PrismaからRDBのViewを参照する

1. はじめに

カラダノートの 堀内 です。 TypeScriptの次世代ORMである Prisma から、RDB側で作成したViewを参照する要件がありました。

せっかくなのでその手順をチュートリアルにします。

2. Viewを参照するチュートリアル

公式にPostgreSQLを例にしたドキュメントがあるので、そちらを参考にします。

www.prisma.io

RDB側の準備

まず RDB側に Viewを作成します。

公式のクイックスタート からサンプルプロジェクトをダウンロードします。

curl -L https://pris.ly/quickstart | tar -xz --strip=2 quickstart-master/typescript/starter

サンプルプロジェクトのprisma/dev.dbSQLiteのDBが以下の構造で同梱されています。

f:id:karadanote:20210416112450p:plain

PostとUserはPost.autherIdとUser.idでリレーションが構成されています。この2つのテーブルを結合した以下のようなViewを作成してみます。

f:id:karadanote:20210416112728p:plain

以下のコマンドからViewを作成します。

sqlite3 dev.db
CREATE VIEW view_posts AS SELECT Post.id, Post.title, Post.content, Post.published, Post.authorId, User.email, User.name from Post LEFT JOIN User on Post.authorId = User.id;

Viewが作成されたか確認してみます。

f:id:karadanote:20210416112825p:plain

Prisma側の準備

サンプルプロジェクトのprisma/schema.prismaに↑で作成したViewのモデル定義を記述します。

通常のテーブルであれば、npx prisma introspectRDBから自動的に検出してschema.prismaに記載してくれるのですが、現時点では以下の公式ドキュメントの記述どおり、Viewのintrospectionはサポートされていません。

You must manually add each view as a model to the Prisma schema right now. Introspection does not add views to the schema currently.

なので、自分で書きます。

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  Int?
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

+ model view_posts {
+   id        Int     @unique
+   title     String
+   content   String?
+   published Boolean @default(false)
+   authorId  Int?
+   email String
+   name  String?
+ }

ここで2つポイントがあります。

  • モデル名はView名と同じにする
    • 同じにしないと does not exist in the current database. がでます
  • 何らかのカラムが必ずユニークになるようにする
    • @idや@uniqueを最低でも1つ指定します

コードからViewを呼ぶ

サンプルプロジェクトの script.ts から↑で作成したモデル定義を使ってViewを読んでみます。

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  const posts = await prisma.view_posts.findMany()
  console.log(posts)
}

main()
  .catch(e => {
    throw e
  })
  .finally(async () => {
    await prisma.$disconnect()
  })

↑のコードを実行してみると、Viewのデータを参照できていることが確認できます。

eiichihoriuchi@EiichinoMacBook-Pro starter % npm run dev        

> script@ dev /Users/eiichihoriuchi/workspace/starter2/starter
> ts-node ./script.ts

[
  {
    id: 1,
    title: 'Hello World',
    content: null,
    published: false,
    authorId: 2,
    email: 'maria@prisma.io',
    name: 'Maria'
  }
]

3. おわりに

Viewを活用してコードから参照できれば、コードの中に複雑なSQLやデータの整形を書かずに済むので見通しが良くなりますよね。 GraphQLでもいいんですが、サブクエリや集計を利用するケースではRDBにもメリットがありますね。