IlmHamroh
JavaScript Full-stack/27-qism. Ma'lumotlar bazasi chuqur: Postgres, ORM, Mongo, Redis23/65-dars19 daqiqa
Mundarija (31)

Prisma Client CRUD: findUnique, create, update, upsert va delete

Qisqacha: Prisma Client har model uchun tayyor metodlar beradi: o'qish — findUnique, findFirst, findMany; yozish — create, createMany, update, upsert, delete. Har biri tipli: noto'g'ri maydon nomi kompilyatsiyada ushlanadi. Qaysi ustunlar qaytishini select, include va omit boshqaradi. Topilmagan yozuvni yangilash yoki o'chirish P2025 xatosini beradi. Ilovada PrismaClient bitta bo'lishi kerak: biz o'lchadik — 5 ta klient 15 ta ulanish ochdi, bittasi 3 ta.

Bu darsda

  • Prisma Client'ning o'qish va yozish metodlarini ishlatasiz va har biri yuborgan SQL'ni o'qiysiz.
  • select, include va omit farqini tushuntira olasiz.
  • P2025 va P2002 xatolarini taniysiz va HTTP javobga aylantirasiz.
  • upsert poygasini (race condition) ko'rasiz va qayta urinish bilan hal qilasiz.
  • Nega PrismaClient ilovada bitta bo'lishi kerakligini o'lchov bilan isbotlaysiz.

Oldin bilishingiz kerak: Prisma: relations, Prisma 7: o'rnatish va arxitektura, INSERT va RETURNING, UPSERT va MERGE, Connection pooling.

1. Nega bu kerak?

Oldingi darsda schema.prisma dagi modellar va ular orasidagi bog'lanishlarni yozdik. prisma generate shu sxemadan klient yasadi — src/generated/prisma papkasidagi TypeScript kod. Endi uni ishlatish vaqti.

26-qismdagi PgOmbori da SQL qo'lda edi, natija esa any ga yaqin obyekt — ustun nomidagi xato faqat ishga tushganda chiqardi. Prisma Client'da siz obyekt yozasiz: db.vazifa.findUnique({ where: { id: 2n } }). Prisma undan SQL yasaydi, parametrlarni alohida yuboradi va natijani tipli obyektga aylantiradi. Maydon nomi xato bo'lsa — muharrir darhol qizil chiziq tortadi.

O'xshatish: restoranda oshpazga o'zingiz retsept aytsangiz — bu raw SQL. Menyudan tanlasangiz — bu Prisma. Menyuda yo'q taomni so'rab bo'lmaydi, lekin menyudagisini xato buyurtma qilish ham qiyin.

Bu darsdagi hamma chiqishlarni biz ishga tushirdik: PostgreSQL 17.6 (holat 20 sxemasi va namunasi), Prisma 7.10.1, Node 24.21.0. SQL — log: query hodisasidan, "public". prefiksisiz; bigint id'lar JSON'da "2n" ko'rinishida.

2. Klient va ulanish

2.1 Klientni yaratish

Prisma 7 da klient bazaga o'zi ulanmaydi — unga adapter beriladi (Prisma 7 arxitekturasi). Adapter ichida pg ning hovuzi (pool) ishlaydi:

ts
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "./generated/prisma/client.ts";

const db = new PrismaClient({
  adapter: new PrismaPg({
    connectionString: process.env["DATABASE_URL"],
  }),
});

Har model klientda kichik harf bilan boshlanadigan xususiyat bo'ladi: Vazifa → db.vazifa, Foydalanuvchi → db.foydalanuvchi. Metodlar hamma modelda bir xil.

flowchart LR
  K["Kod: db.vazifa.create()"] --> P["Prisma Client<br/>(query compiler)"]
  P --> A["PrismaPg adapter"]
  A --> H["pg.Pool (hovuz)"]
  H --> DB[("PostgreSQL")]

Diagrammaga qarang: siz faqat chap tomondagi obyekt bilan ishlaysiz. SQL yasash — Prisma'ning ishi, ulanishlarni boshqarish — pg hovuzining ishi.

2.2 Singleton: klient bitta bo'lsin

Har new PrismaClient(...) o'zining alohida hovuzini ochadi. Agar har HTTP so'rovda yangi klient yaratilsa, ulanishlar soni tez o'sadi. Biz o'lchadik: har biri 3 ta parallel so'rov bajargan 5 ta klient va 5 marta ishlatilgan bitta klient. Ulanishlarni pg_stat_activity dan sanadik:

text
boshida: 0
5 ta klient (har biri 3 parallel so'rov): 15
bitta klient (5 marta): 3

5 ta klient 15 ta ulanish ochdi. Bitta klientga 3 ta yetdi: bir vaqtda 3 ta so'rov bor edi. Postgres'da max_connections sukut bo'yicha 100 (Postgres arxitekturasi). Har so'rovga yangi klient yaratilsa, server tez orada "too many clients" deydi.

Singleton (yagona nusxa) — dasturda obyekt faqat bir marta yaratilib, hamma joyda o'sha ishlatiladigan naqsh. Qoida oddiy: klient ilova ishga tushganda bir marta yaratiladi va bog'liqlik sifatida uzatiladi.

Maslahat: Ba'zi dev-serverlar (masalan 23-qismdagi Next.js) fayl o'zgarganda modulni qayta yuklaydi va har safar yangi klient yaratadi. Shuning uchun internetda globalThis.prisma ??= new PrismaClient(...) naqshini uchratasiz. Express loyihamizda bunga hojat yo'q: klientni omborYarat bir marta yaratadi.

Tekshirib ko'ring: Server 20 ta so'rovni parallel qabul qildi va har biri new PrismaClient() yaratdi, lekin $disconnect() chaqirmadi. Nima bo'ladi?

Javob

Har klient o'z hovuzida kamida bitta ulanish ochadi va yopmaydi. Ulanishlar yig'ilib boradi; bir necha yuz so'rovdan keyin Postgres yangi ulanishni rad etadi. Bitta umumiy klient bilan esa ulanishlar soni hovuz chegarasidan oshmaydi.

3. O'qish

3.1 findUnique — noyob kalit bo'yicha

findUnique faqat noyob maydon bo'yicha qidiradi: birlamchi kalit yoki @unique/@@unique. Javob — bitta obyekt yoki null:

ts
const v = await db.vazifa.findUnique({
  where: { id: 2n },
  select: { id: true, matn: true, tartib: true },
});

Natija va Prisma yuborgan SQL:

text
{"id":"2n","matn":"Postgres'ni Docker'da ko'tarish #o'qish #docker","tartib":2}
SQL: SELECT "vazifalar"."id", "vazifalar"."matn", "vazifalar"."tartib" FROM "vazifalar" WHERE ("vazifalar"."id" = $1 AND 1=1) LIMIT $2 OFFSET $3

Uchta narsaga qarang. id — 2n: Prisma BigInt ustunni JavaScript bigint qilib qaytaradi (Prisma: modellar). Qiymatlar $1, $2 parametr — injection mumkin emas (SQL injection). select da so'ralgan uchta ustungina SQL'ga tushdi.

Yo'q id bilan findUnique xato bermaydi — null qaytaradi. "Topilmasa — xato" kerak bo'lsa, findUniqueOrThrow:

text
null
PrismaClientKnownRequestError P2025: An operation failed because it depends on one or more records that were required but not found. No record was found for a query.

3.2 Tarkibiy noyob kalit

Sxemada @@unique([id, egasiId]) bor (Prisma: relations). Prisma undan id_egasiId nomli kalit yasaydi:

ts
await db.vazifa.findUnique({
  where: { id_egasiId: { id: 2n, egasiId: 1n } },
  select: { matn: true },
});

2-vazifa Azizniki (egasi 1). Xuddi shu so'rov egasiId: 3n bilan — null:

text
{"matn":"Postgres'ni Docker'da ko'tarish #o'qish #docker"}
null

Bu — begona vazifani "id ni taxmin qilib" o'qishdan himoyaning bir usuli: vazifa faqat egasi bilan birga qidiriladi.

Prisma 5 dan beri findUnique ning where iga noyob kalitdan tashqari oddiy shartlar ham qo'shsa bo'ladi. Masalan Savatdagi (yumshoq o'chirilgan) 8-vazifa:

ts
await db.vazifa.findUnique({
  where: { id: 8n, ochirilganVaqt: null },
});
text
null
SQL: … WHERE ("vazifalar"."id" = $1 AND "vazifalar"."ochirilgan_vaqt" IS NULL) LIMIT $2 OFFSET $3

8-vazifa bor, lekin o'chirilgan — shart bajarilmadi, null. Noyob kalit (id) baribir majburiy: usiz findUnique yozib bo'lmaydi.

3.3 findFirst va findMany

findFirst — istalgan shart bo'yicha birinchi mos yozuv. "Birinchi" ma'noli bo'lishi uchun doim orderBy bering (ORDER BY):

ts
await db.vazifa.findFirst({
  where: { egasiId: 1n, bajarildi: true },
  orderBy: { id: "asc" },
  select: { id: true, matn: true },
});
text
{"id":"2n","matn":"Postgres'ni Docker'da ko'tarish #o'qish #docker"}
SQL: SELECT … WHERE ("vazifalar"."egasi_id" = $1 AND "vazifalar"."bajarildi" = $2) ORDER BY "vazifalar"."id" ASC LIMIT $3 OFFSET $4

findMany — massiv. take — nechta olish (SQL'da LIMIT):

ts
await db.vazifa.findMany({
  where: { egasiId: 2n },
  orderBy: { id: "asc" },
  take: 3,
  select: { id: true, matn: true, bajarildi: true },
});
text
[{"id":"5n","matn":"Ingliz tili: 20 ta yangi so'z #o'qish #ingliz","bajarildi":true},{"id":"14n","matn":"IELTS writing task 2 mashqi #ingliz","bajarildi":false},{"id":"16n","matn":"Bozordan meva olish #bozor","bajarildi":true}]

Hech narsa topilmasa findMany bo'sh massiv [] qaytaradi, findFirst esa null. Filtrlar, sahifalash va agregatlar — keyingi darsda.

Tekshirib ko'ring: Nega findUnique({ where: { matn: "Non olish" } }) deb yozib bo'lmaydi?

Javob

matn sxemada @unique emas (noyoblik bazada murakkab EXCLUDE cheklovi bilan, faqat faol vazifalar orasida — Prisma uni bilmaydi). findUnique faqat noyob kalitni qabul qiladi, aks holda TypeScript xato beradi. Matn bo'yicha qidirish — findFirst yoki findMany.

4. Qaysi ustunlar qaytadi: select, include, omit

4.1 include — hamma skalyar maydon + relation

ts
const f = await db.foydalanuvchi.findUniqueOrThrow({
  where: { id: 3n },
  include: { teglar: true },
});
console.log(Object.keys(f).join(", "));
text
id, email, ism, yaratilganVaqt, vazifalarVersiyasi, sozlamalar, teglar
SQL: SELECT "foydalanuvchilar"."id", "foydalanuvchilar"."email", "foydalanuvchilar"."ism", "foydalanuvchilar"."yaratilgan_vaqt", "foydalanuvchilar"."vazifalar_versiyasi", "foydalanuvchilar"."sozlamalar" FROM "foydalanuvchilar" WHERE … LIMIT $2 OFFSET $3
SQL: SELECT "teglar"."id", "teglar"."egasi_id", "teglar"."nom" FROM "teglar" WHERE "teglar"."egasi_id" = $1 OFFSET $2

Ikkita SQL: avval foydalanuvchi (hamma ustunlar, sozlamalar jsonb ham), keyin uning teglari. Prisma relation'ni sukut bo'yicha alohida so'rov bilan oladi — Prisma: relations darsidagi "1+1+1". JOIN bilan yuklashni prodakshn darsida solishtiramiz.

4.2 select — faqat kerakli maydonlar

ts
await db.foydalanuvchi.findUniqueOrThrow({
  where: { id: 3n },
  select: {
    ism: true,
    teglar: { select: { nom: true }, orderBy: { nom: "asc" } },
  },
});
text
{"ism":"Sardor Tursunov","teglar":[{"nom":"#ish"},{"nom":"#mashina"},{"nom":"#sport"},{"nom":"#to'lov"},{"nom":"#uy"}]}
SQL: SELECT "foydalanuvchilar"."id", "foydalanuvchilar"."ism" FROM "foydalanuvchilar" WHERE … LIMIT $2 OFFSET $3
SQL: SELECT "teglar"."id", "teglar"."nom", "teglar"."egasi_id" FROM "teglar" WHERE "teglar"."egasi_id" = $1 ORDER BY "teglar"."nom" ASC OFFSET $2

SQL'da id va egasi_id ham bor — Prisma ularni ikki natijani bog'lash uchun o'zi qo'shadi, lekin sizga qaytarmaydi. Natija obyektining turi ham select ga qarab yasaladi: f.email ni yozsangiz — TypeScript xato beradi, chunki email so'ralmagan.

Bitta so'rovda select va include ni birga ishlatib bo'lmaydi. Qoida: API javobi uchun — doim select (ortiqcha ustun tarmoqqa chiqmaydi), ichki hisob-kitob uchun — include yoki to'liq obyekt.

4.3 omit — "hammasi, faqat bundan tashqari"

Parol hash'i kabi maydonni hech qachon qaytarmaslik kerak bo'lsa, select da qolgan 10 ta maydonni sanab chiqish noqulay. omit teskari ishlaydi:

ts
await db.foydalanuvchi.findUniqueOrThrow({
  where: { id: 1n },
  omit: { sozlamalar: true, vazifalarVersiyasi: true },
});

Global omit — klient darajasida, hamma so'rov uchun; kerak bo'lsa so'rovda false bilan qaytariladi:

ts
const db2 = new PrismaClient({
  adapter,
  omit: { foydalanuvchi: { sozlamalar: true } },
});
await db2.foydalanuvchi.findUniqueOrThrow({ where: { id: 1n } });
await db2.foydalanuvchi.findUniqueOrThrow({
  where: { id: 1n },
  omit: { sozlamalar: false },
});

Uchala so'rov qaytargan kalitlar:

text
id, email, ism, yaratilganVaqt
id, email, ism, yaratilganVaqt, vazifalarVersiyasi
id, email, ism, yaratilganVaqt, vazifalarVersiyasi, sozlamalar

28-qismda foydalanuvchiga parol hash'i qo'shilganda aynan global omit uni tasodifan API javobiga tushishdan saqlaydi.

5. Yozish

5.1 create — trigger qiymatlari ham qaytadi

ts
await db.vazifa.create({
  data: { egasiId: 2n, matn: "Prisma bilan yaratildi #prisma" },
  select: {
    id: true, tartib: true, versiya: true, izohlarSoni: true,
  },
});
text
{"id":"49n","tartib":7,"versiya":1,"izohlarSoni":0}
SQL: INSERT INTO "vazifalar" ("egasi_id","matn","bajarildi","ustuvorlik","izohlar_soni","versiya") VALUES ($1,$2,$3,CAST($4::text AS "ustuvorlik"),$5,$6) RETURNING "vazifalar"."id", "vazifalar"."tartib", "vazifalar"."versiya", "vazifalar"."izohlar_soni"

tartib ni biz bermadik — uni bazadagi BEFORE INSERT trigger qo'ydi (Dilnozaning 7-vazifasi), va RETURNING uni qaytardi (Triggerlar). E'tibor bering: Prisma sukut qiymatlarni (bajarildi, ustuvorlik, versiya) ham o'zi yubordi — sxemadagi @default dan.

5.2 createMany va createManyAndReturn

Bir nechta qator — bitta INSERT bilan:

ts
await db.vazifa.createMany({
  data: [
    { egasiId: 2n, matn: "Non olish" },
    { egasiId: 2n, matn: "Suv olish" },
  ],
});
text
{"count":2}
SQL: INSERT INTO "vazifalar" (…) VALUES ($1,$2,$3,CAST($4::text AS "ustuvorlik"),$5,$6), ($7,$8,$9,CAST($10::text AS "ustuvorlik"),$11,$12)

createMany faqat sonni qaytaradi. Yaratilgan qatorlar kerak bo'lsa — createManyAndReturn (oxiriga RETURNING qo'shiladi):

text
[{"id":"52n","matn":"Choy olish","tartib":10},{"id":"53n","matn":"Shakar olish","tartib":11}]

skipDuplicates: true — noyoblik buzilsa xato o'rniga o'sha qatorni tashlab ketadi. Dilnozada #prisma tegi (trigger yaratgan) allaqachon bor edi:

ts
await db.teg.createMany({
  data: [
    { egasiId: 2n, nom: "#prisma" },
    { egasiId: 2n, nom: "#yangi" },
  ],
  skipDuplicates: true,
});
text
{"count":1}
SQL: INSERT INTO "teglar" ("egasi_id","nom") VALUES ($1,$2), ($3,$4) ON CONFLICT DO NOTHING

Ikkitadan bittasi qo'shildi. Postgres'ning ON CONFLICT DO NOTHING i (UPSERT va MERGE).

5.3 update va updateMany

ts
await db.vazifa.update({
  where: { id: 49n },
  data: {
    bajarildi: true,
    bajarilganVaqt: new Date("2026-10-25T06:00:00Z"),
  },
  select: { versiya: true, bajarildi: true },
});
text
{"versiya":2,"bajarildi":true}

versiya ni 2 ga trigger oshirdi (optimistik qulf — Optimistic concurrency). update faqat noyob where qabul qiladi va yozuv topilmasa xato beradi:

text
PrismaClientKnownRequestError P2025: An operation failed because it depends on one or more records that were required but not found. No record was found for an update.

updateMany — istalgan shart, xato yo'q, faqat son:

ts
await db.vazifa.updateMany({
  where: { egasiId: 2n, matn: { endsWith: "olish" } },
  data: { ustuvorlik: "past" },
});
text
{"count":4}
SQL: UPDATE "vazifalar" SET "ustuvorlik" = CAST($1::text AS "ustuvorlik") WHERE ("vazifalar"."egasi_id" = $2 AND "vazifalar"."matn"::text LIKE ('%' || $3))

Diqqat: updateMany({ data: … }) — where siz — jadvaldagi hamma qatorni o'zgartiradi. Prisma bunda ogohlantirmaydi. Xuddi SQL'dagi WHERE'siz UPDATE kabi (UPDATE va DELETE xavfsiz).

5.4 upsert — bor bo'lsa yangila, yo'q bo'lsa yarat

ts
await db.teg.upsert({
  where: { egasiId_nom: { egasiId: 2n, nom: "#dokon" } },
  create: { egasiId: 2n, nom: "#dokon" },
  update: {},
  select: { id: true, nom: true },
});

Ikki marta chaqirdik. Natija ikkalasida bir xil, SQL esa har xil:

text
{"id":"35n","nom":"#dokon"}
  SQL: SELECT "teglar"."id" FROM "teglar" WHERE (("teglar"."egasi_id" = $1 AND "teglar"."nom" = $2) AND 1=1) OFFSET $3
  SQL: INSERT INTO "teglar" ("egasi_id","nom") VALUES ($1,$2) RETURNING "teglar"."id"
  SQL: SELECT "teglar"."id", "teglar"."nom" FROM "teglar" WHERE "teglar"."id" = $1 LIMIT $2 OFFSET $3
  SQL: COMMIT
{"id":"35n","nom":"#dokon"}
  SQL: SELECT "teglar"."id" FROM "teglar" WHERE (("teglar"."egasi_id" = $1 AND "teglar"."nom" = $2) AND 1=1) OFFSET $3
  SQL: SELECT "teglar"."id", "teglar"."nom" FROM "teglar" WHERE … AND "teglar"."id" IN ($3)) LIMIT $4 OFFSET $5
  SQL: SELECT "teglar"."id", "teglar"."nom" FROM "teglar" WHERE "teglar"."id" = $1 LIMIT $2 OFFSET $3
  SQL: COMMIT

Muhim topilma: bu upsert Postgres'ning INSERT … ON CONFLICT i emas. Prisma tranzaksiya ichida avval SELECT, keyin INSERT yoki UPDATE qiladi.

Prisma 7 hujjatiga ko'ra, haqiqiy ON CONFLICT ("database upsert") faqat hamma shart bajarilganda ishlatiladi:

  • where da bitta noyob maydon;
  • create va update ichida ichki (nested) yozuv yo'q;
  • select da relation o'qilmaydi va faqat bitta model o'zgaradi.

Bizning where da tarkibiy kalit (egasiId_nom — ikki maydon) — birinchi shart bajarilmadi.

Oqibati — poyga holati (Race condition'larni hal qilish). Bir xil yangi tegga 10 ta parallel upsert:

text
1-urinish: 10 parallel upsert — muvaffaqiyatli 10, xato 0
2-urinish: 10 parallel upsert — muvaffaqiyatli 1, xato 9
   PrismaClientKnownRequestError P2002 (asl 23505): Unique constraint failed on the constraint: `teglar_egasi_id_nom_key`
3-urinish: 10 parallel upsert — muvaffaqiyatli 1, xato 9

Nima bo'ldi? Birinchi urinishda poyga ko'rinmadi: u vaqtga bog'liq, har safar chiqmaydi (shuning uchun xavfli — testda o'tib, prodakshnda chiqadi). Keyingilarida o'ntadan to'qqiztasi bir vaqtda "yo'q" ni ko'rdi, INSERT qildi va noyoblik cheklovi (P2002) ularni to'xtatdi. Ma'lumot buzilmadi, lekin foydalanuvchi 500 xatoni ko'rishi mumkin. Yechim — 3-mashqda.

Qadam 1-chaqiruv 2-chaqiruv Natija
1 SELECT — teg yo'q SELECT — teg yo'q ikkalasi "yarataman" deydi
2 INSERT — muvaffaqiyat INSERT kutadi 1-chaqiruvda yangi teg
3 COMMIT 23505 → P2002 2-chaqiruv yiqildi

5.5 delete va deleteMany

ts
await db.vazifa.delete({
  where: { id: 49n },
  select: { id: true, matn: true },
});
text
{"id":"49n","matn":"Prisma bilan yaratildi #prisma"}
SQL: DELETE FROM "vazifalar" WHERE ("vazifalar"."id" = $1 AND 1=1) RETURNING "vazifalar"."id", "vazifalar"."matn"

delete o'chirilgan yozuvni qaytaradi (RETURNING). Xuddi shu id ni ikkinchi marta — P2025 (No record was found for a delete.). deleteMany — son: {"count":5}. Bazadagi FK ON DELETE CASCADE ishlaydi — izohlar ham o'chadi (Foreign key).

Loyihamizda vazifa haqiqatan o'chirilmaydi: ochir metodi ochirilganVaqt ni qo'yadi — yumshoq o'chirish (Soft delete).

6. Xatolar: P2025 va P2002

Prisma xatolari Prisma.PrismaClientKnownRequestError klassida, code maydoni bilan keladi. Eng ko'p uchraydiganlari:

Kod Ma'nosi HTTP
P2025 kerakli yozuv topilmadi (update/delete/OrThrow) 404
P2002 noyoblik buzildi (meta da cheklov nomi) 409
P2003 tashqi kalit buzildi 409 yoki 422
P2039 bazadagi boshqa xato (masalan CHECK) — asl kod meta ichida 422 yoki 500

upsert poygasidagi P2002 xabarida "asl 23505" bor edi — bu Postgres'ning o'z xato kodi (SQLSTATE). Prisma 7 adapter bilan uni x.meta.driverAdapterError.cause.originalCode ichida saqlaydi. Loyihamizdagi PrismaOmbori aynan shu yo'ldan 23P01 ni (014 dagi EXCLUDE cheklovi) topadi — Prisma bunga alohida P-kod bermaydi.

7. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
P2025 … No record was found for an update update/delete topilmagan id bilan 404 ga aylantiring yoki updateMany ishlating
Do not know how to serialize a BigInt bigint id ni to'g'ridan JSON.stringify Number(id) (xavfsiz chegarani tekshirib) yoki toString()
findUnique da TS xatosi where da noyob kalit yo'q findFirst
too many clients already har so'rovga new PrismaClient() bitta klient
javobda keraksiz ustunlar include yoki select siz API'da doim select / omit
P2002 parallel upsert da Prisma upsert — SELECT + INSERT P2002 da qayta urinish

8. Mashqlar

Mashqlar kurs/mashqlar/27/23-prisma-crud/ da. 2-mashq yechimi Node 24 da tekshirilgan; 3-mashqni biz o'z bazamizda ishga tushirdik.

1-mashq (oson): to'ldiring

  • Topilmasa null qaytaradigan metod — [:findUnique]; topilmasa xato beradigani — [:findUniqueOrThrow]
  • Yaratilgan qatorlarni qaytaradigan ommaviy yaratish —
  • "Hamma ustun, faqat shundan tashqari" —
  • update topilmagan yozuvda beradigan xato kodi —
Yechim
  • findUnique / findUniqueOrThrow — ikkinchisi P2025 beradi.
  • createManyAndReturn — createMany faqat { count } qaytaradi.
  • omit — select ning teskarisi; global ham bo'ladi.
  • P2025 — "kerakli yozuv topilmadi".

2-mashq (o'rta): Prisma xatosini HTTP holatiga

holatKodi(xato) funksiyasini yozing: xato obyektida code bo'lsa — P2025 → 404, P2002 → 409, P2003 → 409; boshqa har qanday holat (shu jumladan oddiy Error) → 500. Prisma klassini import qilmaymiz — faqat code maydoniga qaraymiz (yechim bazasiz ishlaydi).

Yechim
js
const KODLAR = { P2025: 404, P2002: 409, P2003: 409 };

function holatKodi(xato) {
  const kod = xato?.code;
  if (typeof kod === "string" && Object.hasOwn(KODLAR, kod)) {
    return KODLAR[kod];
  }
  return 500;
}

console.log(holatKodi({ code: "P2025" })); // 404
console.log(holatKodi({ code: "P2002" })); // 409
console.log(holatKodi(new Error("tarmoq"))); // 500
console.log(holatKodi({ code: "constructor" })); // 500

Object.hasOwn — "constructor" kabi prototip nomlari tasodifan mos kelmasin (SQL injection darsidagi oq ro'yxat qoidasi). Haqiqiy ilovada avval x instanceof Prisma.PrismaClientKnownRequestError tekshiriladi.

3-mashq (qiyin): poygaga chidamli upsert

tegOl(egasiId, nom) funksiyasini yozing: tegni upsert bilan oladi, P2002 kelsa qayta urinadi (ko'pi bilan 3 marta). 10 ta parallel chaqiruv bilan sinang: hammasi muvaffaqiyatli bo'lsin va bitta id qaytsin.

Yechim
ts
import { PrismaPg } from "@prisma/adapter-pg";
import { Prisma, PrismaClient } from "./generated/prisma/client.ts";

const db = new PrismaClient({
  adapter: new PrismaPg({
    connectionString: process.env["DATABASE_URL"],
  }),
});

async function tegOl(egasiId: bigint, nom: string) {
  const where = { egasiId_nom: { egasiId, nom } };
  for (let urinish = 1; ; urinish++) {
    try {
      return await db.teg.upsert({
        where, create: { egasiId, nom }, update: {},
        select: { id: true },
      });
    } catch (x) {
      const takror = x instanceof Prisma.PrismaClientKnownRequestError
        && x.code === "P2002";
      if (!takror || urinish >= 3) throw x;
    }
  }
}

const natija = await Promise.allSettled(
  Array.from({ length: 10 }, () => tegOl(2n, "#mashq23")),
);
const ok = natija.filter((r) => r.status === "fulfilled");
const idlar = new Set(ok.map((r) => r.value.id.toString()));
console.log(
  "muvaffaqiyatli:", ok.length, "| har xil id:", idlar.size,
);

Biz uch marta ishga tushirdik (PostgreSQL 17.6, Prisma 7.10.1) — uchalasida:

text
muvaffaqiyatli: 10 | har xil id: 1

Nega ishlaydi: P2002 olgan chaqiruv qayta urinishda SELECT qiladi va endi tegni topadi — update yo'li. Qayta urinish faqat P2002 da: boshqa xatoni (masalan ulanish uzilishi) yashirmaymiz. Chegara (3) — cheksiz siklga tushib qolmaslik uchun.

9. Loyiha qadami: PrismaOmbori — klient, yarat va ol

Holat 20 — Prisma 7 ombori. U bir necha darsda quriladi: bu darsda klientni ulaymiz va eng oddiy ikki metodni yozamiz. Kod tip va testlardan to'liq raw SQL darsida o'tadi va commit o'sha yerda — hozir commit qilmang.

9.1 Klient — bitta, omborYarat ichida

src/ishga-tushir.ts dagi omborYarat ga yangi tarmoq (SAQLASH=prisma). Hovuzni biz o'zimiz yaratamiz va adapterga beramiz — timeout'lar, application_name va error tinglovchisi bizning nazoratda bo'lsin (Connection pooling):

ts
case "prisma": {
  if (config.DATABASE_URL === undefined) {
    throw new Error("DATABASE_URL yo'q");
  }
  // pgPool() EMAS — u bigint'ni Number qiladi, Prisma bigint kutadi
  const pool = new pg.Pool({
    connectionString: config.DATABASE_URL,
    application_name: "vazifalar-api",
    max: config.PG_POOL_MAX,
    idleTimeoutMillis: 30_000,
    connectionTimeoutMillis: 5_000,
    statement_timeout: config.PG_STATEMENT_TIMEOUT,
  });
  pool.on("error", (err) => {
    log?.error({ err }, "pg pool (prisma): bo'sh ulanish uzildi");
  });
  const db = new PrismaClient({ adapter: new PrismaPg(pool) });
  const yop = async (): Promise<void> => {
    await db.$disconnect();
    await pool.end();
  };
  const bor = await db.foydalanuvchi.count({
    where: { id: BigInt(config.EGASI_ID) },
  });
  if (bor !== 1) {
    await yop();
    throw new Error(
      `EGASI_ID=${String(config.EGASI_ID)} ` +
        "foydalanuvchilar jadvalida yo'q",
    );
  }
  return { ombor: new PrismaOmbori(db, config.EGASI_ID), yop };
}

Bu — singleton amalda: klient server ishga tushganda bir marta yaratiladi, server to'xtaganda yop() uni yopadi. config.ts dagi SAQLASH ro'yxatiga "prisma" qo'shiladi. Oxirgi tekshiruv — fail-fast: egasi bazada yo'q bo'lsa, server ishga tushmaydi (26-qismdagi postgres tarmog'idagidek).

9.2 PrismaOmbori: yarat va ol

src/vazifalar/prisma-ombor.ts — VazifaOmbori interfeysining yangi amalga oshirilishi. Hozircha boshi (kanondan qisqartirilgan):

ts
const TANLOV = {
  id: true, matn: true, bajarildi: true, tartib: true,
  versiya: true, yaratilganVaqt: true, yangilanganVaqt: true,
} as const;

function son(x: bigint): number {
  const n = Number(x);
  if (!Number.isSafeInteger(n)) {
    throw new RangeError(
      `bigint ${x.toString()} Number'ga sig'maydi`,
    );
  }
  return n;
}

export class PrismaOmbori implements VazifaOmbori {
  readonly #db: PrismaClient;
  readonly #egasi: bigint;

  constructor(db: PrismaClient, egasiId: number) {
    this.#db = db;
    this.#egasi = BigInt(egasiId);
  }

  async ol(id: number): Promise<Vazifa | undefined> {
    const q = await this.#db.vazifa.findFirst({
      where: {
        id: BigInt(id), egasiId: this.#egasi, ochirilganVaqt: null,
      },
      select: TANLOV,
    });
    return q === null ? undefined : vazifa(q);
  }
}

Uch qaror:

  • TANLOV — bitta select obyekti hamma o'qishda. API faqat shu 7 ta maydonni ko'rsatadi; as const turini aniq saqlaydi.
  • son() — bigint → number. API JSON'ida id son (25-qism shartnomasi); 2⁵³ dan katta bo'lsa jim buzilmasin — xato.
  • ol da findFirst — egasiId va ochirilganVaqt shartlari bilan. Begona yoki Savatdagi vazifa — undefined (API'da 404).

vazifa(q) yordamchisi Prisma qatorini API'ning Vazifa turiga aylantiradi (toISOString(), teglar matndan, tartib ?? 0). yarat interaktiv tranzaksiya ichida ishlaydi — uni tranzaksiyalar darsida ko'ramiz.

Hozircha npm run tip xato beradi: royxat, yangila hali yo'q. Bu kutilgan holat — keyingi darslarda to'ldiramiz.

10. Real ishda

  • Prisma — Node/TypeScript backend'da eng ko'p ishlatiladigan ORM'lardan biri; NestJS bilan birga (Prisma bilan NestJS, 30-qism) juda keng tarqalgan.
  • Kod review'da tekshiriladi: API javobida select bormi, bigint to'g'ri o'girilganmi, update P2025 holati 404 ga aylanganmi.
  • Intervyuda: "findUnique va findFirst farqi?", "include va select?", "Nega PrismaClient bitta bo'lishi kerak?", "Prisma upsert atomikmi?"

Xulosa

  • O'qish: findUnique (noyob kalit, null), findFirst (birinchi mos, orderBy bilan), findMany (massiv); …OrThrow — P2025.
  • Yozish: create, createMany/createManyAndReturn, update/updateMany, upsert, delete/deleteMany; …Many faqat son qaytaradi.
  • select — faqat kerakli maydonlar (API uchun), include — hamma + relation, omit — istisno.
  • Prisma upsert bizning holatda SELECT + INSERT — parallel chaqiruvda P2002, qayta urinish kerak.
  • PrismaClient — ilovada bitta: 5 ta klient 15 ulanish, bittasi 3.

Keyingi dars: Prisma: filtrlash, pagination va agregatlar — where operatorlari, some/every/none, NULL tuzog'i, cursor va groupBy.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Prisma Client CRUD: findUnique, create, update, upsert va delete — IlmHamroh