Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Klient va ulanish
- 2.1 Klientni yaratish
- 2.2 Singleton: klient bitta bo'lsin
- 3. O'qish
- 3.1 findUnique — noyob kalit bo'yicha
- 3.2 Tarkibiy noyob kalit
- 3.3 findFirst va findMany
- 4. Qaysi ustunlar qaytadi: select, include, omit
- 4.1 include — hamma skalyar maydon + relation
- 4.2 select — faqat kerakli maydonlar
- 4.3 omit — "hammasi, faqat bundan tashqari"
- 5. Yozish
- 5.1 create — trigger qiymatlari ham qaytadi
- 5.2 createMany va createManyAndReturn
- 5.3 update va updateMany
- 5.4 upsert — bor bo'lsa yangila, yo'q bo'lsa yarat
- 5.5 delete va deleteMany
- 6. Xatolar: P2025 va P2002
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): to'ldiring
- 2-mashq (o'rta): Prisma xatosini HTTP holatiga
- 3-mashq (qiyin): poygaga chidamli upsert
- 9. Loyiha qadami: PrismaOmbori — klient, yarat va ol
- 9.1 Klient — bitta, omborYarat ichida
- 9.2 PrismaOmbori: yarat va ol
- 10. Real ishda
- Xulosa
- Manbalar
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 qaytishiniselect,includevaomitboshqaradi. Topilmagan yozuvni yangilash yoki o'chirish P2025 xatosini beradi. IlovadaPrismaClientbitta 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,includevaomitfarqini tushuntira olasiz.- P2025 va P2002 xatolarini taniysiz va HTTP javobga aylantirasiz.
upsertpoygasini (race condition) ko'rasiz va qayta urinish bilan hal qilasiz.- Nega
PrismaClientilovada 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:
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:
boshida: 0
5 ta klient (har biri 3 parallel so'rov): 15
bitta klient (5 marta): 35 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: klientniomborYaratbir 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:
const v = await db.vazifa.findUnique({
where: { id: 2n },
select: { id: true, matn: true, tartib: true },
});Natija va Prisma yuborgan SQL:
{"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 $3Uchta 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:
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:
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:
{"matn":"Postgres'ni Docker'da ko'tarish #o'qish #docker"}
nullBu — 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:
await db.vazifa.findUnique({
where: { id: 8n, ochirilganVaqt: null },
});null
SQL: … WHERE ("vazifalar"."id" = $1 AND "vazifalar"."ochirilgan_vaqt" IS NULL) LIMIT $2 OFFSET $38-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):
await db.vazifa.findFirst({
where: { egasiId: 1n, bajarildi: true },
orderBy: { id: "asc" },
select: { id: true, matn: true },
});{"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 $4findMany — massiv. take — nechta olish (SQL'da LIMIT):
await db.vazifa.findMany({
where: { egasiId: 2n },
orderBy: { id: "asc" },
take: 3,
select: { id: true, matn: true, bajarildi: true },
});[{"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
const f = await db.foydalanuvchi.findUniqueOrThrow({
where: { id: 3n },
include: { teglar: true },
});
console.log(Object.keys(f).join(", "));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 $2Ikkita 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
await db.foydalanuvchi.findUniqueOrThrow({
where: { id: 3n },
select: {
ism: true,
teglar: { select: { nom: true }, orderBy: { nom: "asc" } },
},
});{"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 $2SQL'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:
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:
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:
id, email, ism, yaratilganVaqt
id, email, ism, yaratilganVaqt, vazifalarVersiyasi
id, email, ism, yaratilganVaqt, vazifalarVersiyasi, sozlamalar28-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
await db.vazifa.create({
data: { egasiId: 2n, matn: "Prisma bilan yaratildi #prisma" },
select: {
id: true, tartib: true, versiya: true, izohlarSoni: true,
},
});{"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:
await db.vazifa.createMany({
data: [
{ egasiId: 2n, matn: "Non olish" },
{ egasiId: 2n, matn: "Suv olish" },
],
});{"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):
[{"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:
await db.teg.createMany({
data: [
{ egasiId: 2n, nom: "#prisma" },
{ egasiId: 2n, nom: "#yangi" },
],
skipDuplicates: true,
});{"count":1}
SQL: INSERT INTO "teglar" ("egasi_id","nom") VALUES ($1,$2), ($3,$4) ON CONFLICT DO NOTHINGIkkitadan bittasi qo'shildi. Postgres'ning ON CONFLICT DO NOTHING i (UPSERT va MERGE).
5.3 update va updateMany
await db.vazifa.update({
where: { id: 49n },
data: {
bajarildi: true,
bajarilganVaqt: new Date("2026-10-25T06:00:00Z"),
},
select: { versiya: true, bajarildi: true },
});{"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:
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:
await db.vazifa.updateMany({
where: { egasiId: 2n, matn: { endsWith: "olish" } },
data: { ustuvorlik: "past" },
});{"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: … })—wheresiz — 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
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:
{"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: COMMITMuhim 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:
whereda bitta noyob maydon;createvaupdateichida ichki (nested) yozuv yo'q;selectda 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:
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 9Nima 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
await db.vazifa.delete({
where: { id: 49n },
select: { id: true, matn: true },
});{"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
nullqaytaradigan metod — [:findUnique]; topilmasa xato beradigani — [:findUniqueOrThrow] - Yaratilgan qatorlarni qaytaradigan ommaviy yaratish —
- "Hamma ustun, faqat shundan tashqari" —
updatetopilmagan yozuvda beradigan xato kodi —
Yechim
- findUnique / findUniqueOrThrow — ikkinchisi P2025 beradi.
- createManyAndReturn —
createManyfaqat{ count }qaytaradi. - omit —
selectning 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
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" })); // 500Object.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
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:
muvaffaqiyatli: 10 | har xil id: 1Nega 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):
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):
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— bittaselectobyekti hamma o'qishda. API faqat shu 7 ta maydonni ko'rsatadi;as constturini aniq saqlaydi.son()—bigint→number. API JSON'ida id son (25-qism shartnomasi); 2⁵³ dan katta bo'lsa jim buzilmasin — xato.oldafindFirst—egasiIdvaochirilganVaqtshartlari 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
selectbormi,bigintto'g'ri o'girilganmi,updateP2025 holati 404 ga aylanganmi. - Intervyuda: "
findUniquevafindFirstfarqi?", "includevaselect?", "Nega PrismaClient bitta bo'lishi kerak?", "Prismaupsertatomikmi?"
Xulosa
- O'qish:
findUnique(noyob kalit,null),findFirst(birinchi mos,orderBybilan),findMany(massiv);…OrThrow— P2025. - Yozish:
create,createMany/createManyAndReturn,update/updateMany,upsert,delete/deleteMany;…Manyfaqat son qaytaradi. select— faqat kerakli maydonlar (API uchun),include— hamma + relation,omit— istisno.- Prisma
upsertbizning 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
- Prisma hujjati — CRUD: https://www.prisma.io/docs/orm/v7/prisma-client/queries/crud (2026-10-25 da ko'rildi)
- Prisma hujjati — Select fields, omit: https://www.prisma.io/docs/orm/v7/prisma-client/queries/select-fields
- Prisma hujjati — Error reference (P2002, P2025): https://www.prisma.io/docs/orm/v7/reference/error-reference
- Prisma hujjati — upsert va database upserts shartlari: https://www.prisma.io/docs/orm/v7/reference/prisma-client-reference#upsert
- Chiqishlar — biz ishga tushirdik: PostgreSQL 17.6, Prisma 7.10.1, Node 24.21.0, Windows 11 (2026-10-25).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!