今天的主題是 TypedSQL
是 primsa
最今提供的功能,他可以自動幫你 auto generate
你的 SQL file
成一個 function
讓你可以在 prisma client
中使用它,同時擁有完好的 type safe
可以用於需要用 arguments
執行 SQL
指令,那我們廢話不多說開始今天的內容。
以下是今天的 model
來當作我們今天 demo
的資料,簡單介紹一下:
User
有多個 post
內容User
有多個追蹤的 trackingEvents
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
age Int
posts Post[]
trackingEvents TrackingEvent[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id],onDelete: Cascade)
authorId Int
createAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model TrackingEvent {
id Int @id @default(autoincrement())
type String
variant String
user User @relation(fields: [userId], references: [id],onDelete: Cascade)
userId Int
createAt DateTime @default(now())
}
接著我們寫一個 main function
當作我們 seed data
的 root cause
,然後 create user
搭配 faker
產生假資料,然後使用 Prisma.validator
確保 input
的 type safe
,同時你會發現有一個特別的參數 skipDuplicates
這是 prisma
對於有 unique
的欄位用來減少資料重複的 argument
,確保資料一制性
// prisma/seed.ts
import { faker } from '@faker-js/faker'
const main = async () => {
await prisma.user.deleteMany()
const usersInput = Prisma.validator(
prisma,
'user',
'createMany'
)({
data: new Array(NUM_USERS).fill('_').map(() => ({
email: faker.internet.email(),
age: faker.helpers.rangeToNumber({ min: 0, max: 99 }),
name: faker.person.fullName()
})),
skipDuplicates: true
})
const users = await prisma.user.createManyAndReturn(usersInput)
}
這邊簡單舉個例子,在用 createMany
的時候難免會有重複的 email
出現,但因為我們的 email
他是 unique
的所以會導致無法 create data
,所以我們需要加上 skipDuplicates:true
讓 prisma client
幫我們移除重複的 email
資料
const createMany = await prisma.user.createMany({
data: [
{ name: 'Bob', email: 'bob@prisma.io' },
{ name: 'Bobo', email: 'bob@prisma.io' }, // Duplicate unique key!
{ name: 'Yewande', email: 'yewande@prisma.io' },
{ name: 'Angelique', email: 'angelique@prisma.io' },
],
skipDuplicates: true, // Skip 'Bobo'
})
所以最終只會新增三筆
{
count: 3
}
接著我們加上 createFunnel
幫我們關聯 posts
data
到 user
enum ExperimentVariant {
BlueBuyButton = "BlueBuyButton",
GreenBuyButton = "GreenBuyButton",
}
const main = async () => {
//..
await createFunnel(users.slice(0, COUNT_BLUE), ExperimentVariant.BlueBuyButton)
await createFunnel(users.slice(COUNT_BLUE), ExperimentVariant.GreenBuyButton)
}
createFunnel
這邊我們遞迴所有 EventType
的類型,然後 connect
對應類型的 event
到每個 user
中
const createFunnel = async (users: User[], variant: ExperimentVariant) => {
for (const event of [
EventType.PageOpened,
EventType.ProductPutInShoppingCart,
EventType.AddressFilled,
EventType.AddressFilled,
EventType.CheckedOut,
]) {
await createEvents(users, variant, event)
}
}
createEvents
就是很單純的用 Prisma.validator
當作 input
,然後在 create trackingEvent
的時候 connect
到 userId
const createEvents = async (users: User[], variant: ExperimentVariant, event: EventType) => {
const eventsData = Prisma.validator(
prisma,
'trackingEvent',
'createMany'
)({
data: users.map(({ id }) => ({
userId: id,
variant,
type: event
}))
})
await prisma.trackingEvent.createMany(eventsData)
}
最後我們在 createFunnel
中加上 createPosts
的邏輯,讓每個 user
可以有不同數量的 posts
const createFunnel = async (users: User[], variant: ExperimentVariant) => {
//..
const randomPostCounts = faker.helpers.rangeToNumber({ min: 0, max: 5 })
for (let i = 0; i < randomPostCounts; i++) {
await createPosts(users)
}
}
createPosts
也是很單純的用 Prisma.validator
然後 create post
後 connect
到 userId
const createPosts = async (users: User[]) => {
const postData = Prisma.validator(
prisma,
'post',
'createMany'
)({
data: users.map(({ id }) => ({
content: faker.lorem.word(20),
title: faker.lorem.word(5),
authorId: id,
}))
})
await prisma.post.createMany(postData)
}
最後別忘記在 package.json
加上 prisma
的 tag
//package.json
"prisma": {
"seed": "tsx prisma/seed.ts"
}
之後執行 seed data
到我們 DB
>npx prisma db seed
然後到 prisma studio
看一下有沒有資料,這樣我們就完成 seed data
的內容了
不知道大家還記不記得,prisma
除了有 prisma client
的 API
可以執行 CRUD
的 DB query
外,還可以執行 raw SQL
的部分,在 prisma client
有一個 $queryRaw
可以直接執行,那這邊我們就簡單查詢一下 Post
的資料
as
當作 model
的 alias
p.id as "postId"
則是將 table
中對應的欄位轉成在 return data
鍾顯看到的名稱是什麼JOIN
將 Post
跟 User
的 table
綁在一起如此就可以看到 post
跟 user
的資料createAt DESC
則是預設排序const prisma = new PrismaClient()
const main = async () => {
const result = await prisma.$queryRaw`Select
p.id as "postId",
p.title as "postTitle",
p.content as "postContent",
p.published
FROM "Post" as p
JOIN "User" as u on p."authorId" = u.id
ORDER By p."createAt" DESC
`
console.log(result)
}
最後執行 index.ts
看結果
>tsx --watch index.ts
如果我們就成功用 prisma
執行 raw SQL
拉~
[
//..
{
postId: 2641,
postTitle: 'atqui',
postContent: 'tamquam',
published: false
},
//..
]
但 raw SQL
的寫法有一個問題就是沒有 type safe
,所以我們需要對照 SQL
的 table
確定欄位有沒有錯誤等等,這樣就變成每次要執行 $queryRaw
都要重新寫一次,這樣也很難 maintain
所以這時候 TypedSQL
就登場拉,他可以幫你把上面的 raw SQl
部分轉成一個 function
之後也方便你重複使用,至於怎麼完成的我們明天再繼續~
✅ 前端社群 :
https://lihi3.cc/kBe0Y