Open26

sqlite3 + prisma challenge

hidetaka okamotohidetaka okamoto

npx prisma migrate dev --name initまでやると、DBが生成される。

% tree -I node_modules 
.
├── package-lock.json
├── package.json
├── prisma
│   ├── dev.db
│   ├── dev.db-journal
│   ├── migrations
│   │   ├── 20220726082340_init
│   │   │   └── migration.sql
│   │   └── migration_lock.toml
│   └── schema.prisma
└── tsconfig.json

3 directories, 8 files
hidetaka okamotohidetaka okamoto

DBをみるには、sqlite3 prisma/dev.dbを実行

sqlite> .tables
Post                User                _prisma_migrations
sqlite> .databases
main: /Users/sandbox/sql/hello-prisma/prisma/dev.db r/w
hidetaka okamotohidetaka okamoto

DB投入スクリプト。

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient()

const main = async () => {
    const user = await prisma.user.create({
        data: {
            name: 'Alice',
            email: 'alice@example.com'
        }
    })
    console.log(user)
}

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

*.prismaで定義してmigrateしておくと、メソッドが生える仕様なのかな。
引数でオブジェクトを投げる形の方が、抜け漏れが起きにくそうな印象はあるかもしれない。

hidetaka okamotohidetaka okamoto

型もあるので、動的に内容が変わるケースとかでもTSで頑張れる。

    const userData: Omit<User, 'id'> = {
        name: "John",
        email: 'john@example.com'
    }
    const user = await prisma.user.create({
        data: userData
    })

AUTOINCREMENTな値(チュートリアルでのid)も必須になるので、こっちでやる時は適宜Omitすることになりそうかも。

hidetaka okamotohidetaka okamoto

SQLでデータをみる。

sqlite> select * from User;
id|email|name
1|alice@example.com|Alice
2|john@example.com|John
3|jane@example.com|Jane
4|Jack85@hotmail.com|Chanelle
hidetaka okamotohidetaka okamoto

Omit<User, 'id'>だとRelation張ってる値はダメっぽい。
クイックスタートだとこれ。

posts Post[]

こんな書き方になる。

    const user = await prisma.user.create({
        data: {
            ...userData,
            posts: {
                
            }
        }
    })
hidetaka okamotohidetaka okamoto

わかった。
Prisma.<ModelName>CreateInputを使うと良さそう。

import { Prisma, PrismaClient } from "@prisma/client";
import { faker } from '@faker-js/faker';

    const userData: Prisma.UserCreateInput = {
        name: faker.name.firstName(),
        email: faker.internet.email(),
        posts: {
            create: {
                 title: faker.lorem.text(),
                 content: faker.lorem.paragraphs(),
                 published: true,
            }
        }
    }
    const user = await prisma.user.create({
        data: userData
    })
hidetaka okamotohidetaka okamoto

データ探索。
どこかしらDynamoDBとかStripeのSearch APIっぽさを感じるので、SQL苦手でも使いやすい感じはあるかもしれない。

    const users = await prisma.user.findMany({
        where: {
            id: {
                lte: 2
            }
        }
    })

日本語のチートシート

https://qiita.com/koffee0522/items/92be1826f1a150bfe62e

hidetaka okamotohidetaka okamoto

DynamoDB GetItem相当のメソッド

    const user = await prisma.user.findUnique({
        where: {
            id: 1
        }
    })
    console.log(user)
hidetaka okamotohidetaka okamoto

reltion先のデータでもクエリできる。


    const users = await prisma.user.findMany({
        where: {
            posts: {
                some: {
                    published: true
                }
            }
        },
        include: {
            posts: true,
        },
    })

このあたり、型定義か英語のDocs読まないとなので難易度ちょっと高いと感じる人はいるかも?

hidetaka okamotohidetaka okamoto

.selectで指定ができる。

    const posts = await prisma.post.findMany({
        where: {
            published_at: {
                gte: dayjs().subtract(1, 'year').toDate()
            }
        },
        select: {
            title: true,
            id: true,
        }
    })

ただしincludeとの併用はできないっぽい。

hidetaka okamotohidetaka okamoto

Faker.jsを使うと無限に遊べる。

https://github.com/faker-js/faker

@faker-js/fakerがコミュニティForkなので、インストールするライブラリを間違えないように注意。

    const userData: Omit<User, 'id'> = {
        name: faker.name.firstName(),
        email: faker.internet.email(),
    }
    const user = await prisma.user.create({
        data: userData
    })
hidetaka okamotohidetaka okamoto

ER図の出力もできるらしい。

https://github.com/keonik/prisma-erd-generator

Install

yarn add -D prisma-erd-generator @mermaid-js/mermaid-cli

schema.prisma

+generator erd {
+  provider = "prisma-erd-generator"
+  output = "../ERD.svg"
+}

Execute

 npx prisma generate

Appendix

https://zenn.dev/terrierscript/articles/2022-03-25-prisma-er-mermaid

hidetaka okamotohidetaka okamoto

ERを先に書いた方がいいのでは・・・って思うので、コードから生成ってどうなんだろ。
「手書きのラフとかでOKになって、清書しなくて済む」とか、「prisma migrate前のレビューに使える」とかそんなところかな?

hidetaka okamotohidetaka okamoto

Migration

公開日を足してみる

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

hidetaka okamotohidetaka okamoto

レコードが既にある場合、エラーが出る。

Error: 
⚠️ We found changes that cannot be executed:

  • Step 0 Added the required column `published_at` to the `Post` table without a default value. There are 4 rows in this table, it is not possible to execute this step.

optionalにするか、@defaultで初期値を入れる。

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

これでいける。

% npx prisma migrate dev --name add_published_at
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:./dev.db"

Applying migration `20220726132000_add_published_at`

The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20220726132000_add_published_at/
    └─ migration.sql

Your database is now in sync with your schema.

✔ Generated Prisma Client (4.1.0 | library) to ./node_modules/@prisma/client in 
65ms
✔ Generated Entity-relationship-diagram to ./ERD.png in 1.75s

hidetaka okamotohidetaka okamoto

DateTimeDateオブジェクトをセットする。

    const userData: Prisma.UserCreateInput = {
        name: faker.name.firstName(),
        email: faker.internet.email(),
        posts: {
            create: {
                 title: faker.lorem.text(),
                 content: faker.lorem.paragraphs(),
                 published: true,
                 published_at: dayjs().subtract(2, 'month').toDate(),
            }
        }
    }