# prisma ###### tags: `db` ```prisma model Session { id String @id @default(cuid()) sessionToken String @unique userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) } //@relation用法 @relation(_ name: String?, fields: FieldReference[]?, references: FieldReference[]?, onDelete: ReferentialAction?, onUpdate: ReferentialAction?, map: String?) ``` ReferentialAction: Cascade(referenceid 資料刪除對應的reference也會delete)| NoAction [@relation](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#relation) ### uuid type @db.Uuid 這個decorator只能使用在postgresql 中使用讓id生成是uuid type ```typescript model User { id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid test String? } ``` ```typescript model User{ id Int @id @default(autoincrement()) name String @map("Name") firstName String @map("first_name") Post_Post_authorToUser Post[] @relation("Post_authorToUser") Post_Post_favoritedByToUser Post[] @relation("_Post_favoritedByToUser") @@map("users") } //SQL result -- CreateTable CREATE TABLE "users" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "Name" TEXT NOT NULL, "first_name" TEXT NOT NULL ); model Post{ id Int @id @default(autoincrement()) author Int favoritedBy Int? User_Post_authorToUser User @relation("Post_authorToUser",fields: [author],references: [id],onDelete: NoAction,onUpdate: NoAction) User_Post_favoritedByToUser User? @relation("_Post_favoritedByToUser",fields: [favoritedBy],references: [id],onDelete: NoAction,onUpdate: NoAction) } // SQL result CREATE TABLE "Post" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "author" INTEGER NOT NULL, "favoritedBy" INTEGER, CONSTRAINT "Post_author_fkey" FOREIGN KEY ("author") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "Post_favoritedBy_fkey" FOREIGN KEY ("favoritedBy") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION ); ``` * `@@map("users")` : 更改table name * `name String @map("Name")` : 更改 field name ### Enabling full-text search For POSTGRESQL ```typescript //schema.prisma generator client { provider = "prisma-client-js" previewFeatures = ["fullTextSearch"] } ``` For MySQL ``` generator client { provider = "prisma-client-js" previewFeatures = ["fullTextSearch", "fullTextIndex"] } ``` ``` // All posts that contain the word 'cat'. const result = await prisma.posts.findMany({ where: { body: { search: 'cat', }, }, }) ``` ``` // All posts that contain the words 'cat' or 'dog'. const result = await prisma.posts.findMany({ where: { body: { search: 'cat | dog', }, }, }) // All drafts that contain the words 'cat' and 'dog'. const result = await prisma.posts.findMany({ where: { status: 'Draft', body: { search: 'cat & dog', }, }, }) ``` ## mongodb null type filter ```typescript model User { id String @id @default(auto()) @map("_id") @db.ObjectId email String name String? } ``` 在 prisma 如果你的 field 是 optional 你可以加 null 去塞值這時 `prisma studio` 跟 `mongodb` 結果如下: ```typescript const createNull = await prisma.user.create({ data: { email: 'user1@prisma.io', name: null, }, }) console.log(createNull) ``` ```typescript // in prisma studio { id: '6242c4ae032bc76da250b207', email: 'user1@prisma.io', name: null } // in mongodb { id: '6242c4ae032bc76da250b207', email: 'user1@prisma.io', name: null } ``` 但在 `prisma` 中 optional 的欄位其實你可以不加 `prisma` 會自動幫你補 null ```typescript const createMissing = await prisma.user.create({ data: { email: 'user2@prisma.io', }, }) console.log(createMissing) ``` ```typescript // in prisma studio { id: '6242c4ae032bc76da250b208', email: 'user2@prisma.io', name: null } ``` 此時你到 mongodb 查詢你會發現 name 欄位並不會幫你自動加 null ```typescript // in mongodb { "_id": "6242c4af032bc76da250b208", "email": "user2@prisma.io" } ``` 這部分是因為 `prisma` 目前無法關聯 null 到 mongodb 中,此時如果你做 query 查詢你會發現結果並不是你的預期: ```typescript const findNulls = await prisma.user.findMany({ where: { name: null, }, }) console.log(findNulls) ``` 這時你只會返回 user1 的資料 user2 則沒有,原因是所有的 query 查詢都會是直接從 db 拿資料,而 `prisma studio` 只是一個查看的 `dashbord` 而已,所以你無法從 studio 確定資料正確性,實際上還是要從 db 查看。 ```typescript [ { id: '6242c4ae032bc76da250b207', email: 'user1@prisma.io', name: null } ] ``` 所以這時你需要加一個查詢條件 `isSet: false` 這是 `mongodb` 限定的 `filter` ```typescript const result = await prisma.user.findFirst({ where: { OR: [ { name: null }, { name: { isSet: false } } ] } }) ``` 這樣所有欄位都包含到拉~ ```typescript [ { id: '6242c4ae032bc76da250b207', email: 'user1@prisma.io', name: null } , { id: '6242c4ae032bc76da250b208', email: 'user2@prisma.io', name: null } ] ``` ## type safe 大家對於 typesafe 得第一直覺應該是 ts ,沒錯 ts 確實幫我們處理 type 的一切事情,但這僅限在開發模式,什麼意思呢?原因是 ts 只是一個型別推論 ( type inference ) 與型別註記 ( type annotation ) ,他並不能保證 runtine time 不會有預期外的 input 輸入,而 prisma 有一個 validate 的機制,概念就是會檢查 ORM 的 input ,確定沒問題才會寫入 db 。 而 `type safe` 本質上就是需要確保 `runtime `沒有意外 `input`,而 `typescript` 顯然是不行如下圖。 ![](https://hackmd.io/_uploads/S1CMzIbe6.png) 這邊我們就使用一套有 `typesafe` 的 `tool` `zod`來幫我們完成這件事。 ## schema ```typescript // prisma.schema model Product { id String @id @default(cuid()) slug String name String description String price Int } ``` ## validate 這邊我們可以透過 `zod` 寫 `product` 得 `validate` 判斷,然後透過 `Prisma.defineExtension` 去加入我們 `input validate` 的 `function`,當成 `prisma` 得 `extension` 給之後的 `prisma client` 引用。 ```typescript import { Prisma } from "@prisma/client"; import { z } from "zod"; const productSchema = z.object({ slug: z.string().max(100) .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/), name: z.string().max(100), description: z.string().max(100), price: z.number() }) satisfies z.Schema<Prisma.ProductCreateInput> // check object type instead of interface type export const ProductValidation = Prisma.defineExtension({ query: { product: { create: ({ args, query }) => { args.data = productSchema.parse(args.data) return query(args) }, update: ({ args, query }) => { args.data = productSchema.partial().parse(args.data) return query(args) }, updateMany: ({ args, query }) => { args.data = productSchema.partial().parse(args.data) return query(args) }, upsert: ({ args, query }) => { args.create = productSchema.parse(args.create) args.update = productSchema.partial().parse(args.create) return query(args); } } } }) ``` ## use extenstions 接著把 `ProductValidation` 加到 `prisma client` 中,如此一來就完成拉~最後我們看一下結果。 ```typescript import { ProductValidation } from "./model/product"; import { ZodError } from "zod"; const prisma = new PrismaClient() .$extends(ProductValidation) const main = async () => { await prisma.product.deleteMany() // validate product const product = await prisma.product.create({ data: { slug: "example-product", name: "Example Product", description: "Lorem ipsum dolor sit amet", price: 100 } }) try { const inValidateProduct = await prisma.product.create({ data: { slug: "example-", name: "Example Product", description: "Lorem ipsum dolor sit amet", price: 100 } }) } catch (e) { if (e instanceof ZodError) { console.log(e.errors[0]) } } console.log(product) } ``` 如此一來我們就可以確保寫入 `db`的 `input` 值都是預期的~而 `typeSafe` 不僅僅只是檢查型別而已,甚至可以判斷資料大小或是其他 `regex` 的使用,如`productSchema` 的寫法,所以 `typesafe` 在使用上靈活度是非常高的。 ```typescript { validation: 'regex', code: 'invalid_string', message: 'Invalid', path: [ 'slug' ] } { id: 'cln1eh4br0000uaex7qpt7jrq', slug: 'example-product', name: 'Example Product', description: 'Lorem ipsum dolor sit amet', price: 100 } ```