iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0
Modern Web

一些讓你看來很強的 ORM - prisma系列 第 25

Day25. 一些讓你看來很強的 ORM - prisma (TypeSQL)上

  • 分享至 

  • xImage
  •  

今天的主題是 TypedSQLprimsa 最今提供的功能,他可以自動幫你 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())
}

Seed Data

接著我們寫一個 main function 當作我們 seed dataroot cause ,然後 create user 搭配 faker 產生假資料,然後使用 Prisma.validator 確保 inputtype 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:trueprisma 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 datauser


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 的時候 connectuserId

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 postconnectuserId

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 加上 prismatag

//package.json
"prisma": {
    "seed": "tsx prisma/seed.ts"
  }

之後執行 seed data 到我們 DB

>npx prisma db seed

然後到 prisma studio 看一下有沒有資料,這樣我們就完成 seed data 的內容了
https://ithelp.ithome.com.tw/upload/images/20241010/20145677e3PWTo1vSJ.png

QueryRaw

不知道大家還記不記得,prisma 除了有 prisma client 的 API 可以執行 CRUDDB query 外,還可以執行 raw SQL 的部分,在 prisma client 有一個 $queryRaw 可以直接執行,那這邊我們就簡單查詢一下 Post 的資料

  • as 當作 modelalias
  • p.id as "postId" 則是將 table 中對應的欄位轉成在 return data 鍾顯看到的名稱是什麼
  • JOINPostUsertable 綁在一起如此就可以看到 postuser 的資料
  • 最後 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 ,所以我們需要對照 SQLtable 確定欄位有沒有錯誤等等,這樣就變成每次要執行 $queryRaw 都要重新寫一次,這樣也很難 maintain 所以這時候 TypedSQL 就登場拉,他可以幫你把上面的 raw SQl 部分轉成一個 function 之後也方便你重複使用,至於怎麼完成的我們明天再繼續~

大家如果有問題可以來小弟的群組討論~

✅ 前端社群 :
https://lihi3.cc/kBe0Y


上一篇
Day24. 一些讓你看來很強的 ORM - prisma (Count & Distinct)
下一篇
Day26. 一些讓你看來很強的 ORM - prisma (TypeSQL)下
系列文
一些讓你看來很強的 ORM - prisma30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言