Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. $queryRaw: tagged template
- 2.1 Qiymat — parametr
- 2.2 Natija turlari
- 2.3 Nom — parametr emas
- 3. Bo'laklardan yig'ish
- 3.1 Prisma.sql va Prisma.join
- 3.2 Prisma.raw — faqat oq ro'yxatdan
- 4. $executeRaw — o'zgartirish, son qaytaradi
- 5. $queryRawUnsafe — matn sifatida SQL
- 6. TypedSQL: .sql fayldan tipli funksiya
- 6.1 Muammo va g'oya
- 6.2 SQL fayl va generatsiya
- 6.3 Ishlatish
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): xavfsizmi?
- 2-mashq (o'rta): o'z sql tagingiz
- 3-mashq (qiyin): TypedSQL bilan statistika
- 9. Loyiha qadami: PrismaOmbori yakuni va holat 20 commit
- 9.1 teglar() — uch jadval bo'yicha guruhlash
- 9.2 Matn bo'yicha saralash — COLLATE "C"
- 9.3 almashtirHammasi — unnest
- 9.4 Testlar va tekshiruv
- 9.5 Commit
- 10. Real ishda
- Xulosa
- Manbalar
Prisma raw SQL: $queryRaw, Prisma.sql, $executeRaw va TypedSQL
Qisqacha: ORM yetmagan joyda Prisma SQL'ni o'zingiz yozishga ruxsat beradi:
db.$queryRaw`SELECT … WHERE id = ${id}`— bu tagged template,${}ichidagi qiymat matnga yopishmaydi,$1parametr bo'ladi. Bo'laklarni yig'ish —Prisma.sql, ro'yxat —Prisma.join, ustun nomi — faqat oq ro'yxatdanPrisma.raw.$queryRawUnsafega matn ulasangiz — injection: biz sinadik, 1 foydalanuvchi o'rniga 48 qator. TypedSQL esa.sqlfayldan tipli funksiya yasaydi.
Bu darsda
$queryRawva$executeRawbilan parametrli SQL yozasiz.Prisma.sql,Prisma.join,Prisma.rawbilan dinamik so'rovni xavfsiz yig'asiz.- Raw natijadagi turlarni (bigint, Decimal, Date) to'g'ri qayta ishlaysiz.
$queryRawUnsafeqachon xavfli ekanini isbotlaysiz.- TypedSQL bilan SQL faylidan tipli funksiya yasaysiz.
PrismaOmborini tugatib, holat 20 ni commit qilasiz.
Oldin bilishingiz kerak: Prisma: nested write va tranzaksiyalar, Parametrli so'rovlar va SQL injection, Tagged template literals, UPSERT va MERGE.
1. Nega bu kerak?
Oldingi darslarda Prisma ba'zi narsalarni qila olmasligini ko'rdik:
FOR UPDATEqulfi yo'q (tranzaksiyalar darsi).orderByda collation tanlab bo'lmaydi (filtrlash darsi).groupByrelation orqali (teglar → vazifalar) guruhlay olmaydi.updateManyhar qatorga har xil qiymat bera olmaydi.
Bular uchun Prisma eshik qoldirgan — raw SQL. Eshik ikki xil: biri xavfsiz (tagged template), biri xavfli (Unsafe). Qaysi biri qachon — shu dars.
O'xshatish: avtomatik kofe mashinasi 10 xil ichimlik qiladi. 11-chisi kerak bo'lsa, qo'lda tayyorlaysiz — lekin mashinaning o'lchov idishi va filtridan foydalanasiz. Qo'lingizda bo'lsa ham, qoidasi o'sha.
Chiqishlarni biz ishga tushirdik: PostgreSQL 17.6, holat 20 namunasi, Prisma 7.10.1, Node 24.21.0.
2. $queryRaw: tagged template
2.1 Qiymat — parametr
const teg = "#o'qish";
const egasi = 1;
await db.$queryRaw<{ id: bigint; matn: string }[]>`
SELECT id, matn FROM vazifalar
WHERE egasi_id = ${egasi} AND strpos(matn, ${teg}) > 0
ORDER BY id LIMIT 3`;[{"id":"1n","matn":"JavaScript kursining 26-qismini boshlash #o'qish"},{"id":"2n","matn":"Postgres'ni Docker'da ko'tarish #o'qish #docker"},{"id":"34n","matn":"SQL JOIN mashqlarini yechish #o'qish"}]
SQL: SELECT id, matn FROM vazifalar WHERE egasi_id = $1 AND strpos(matn, $2) > 0 ORDER BY id LIMIT 3${egasi} va ${teg} SQL matniga tushmadi — ular $1, $2 bo'ldi va alohida yuborildi. Bu oddiy template literal emas: $queryRaw — tag funksiyasi, u matn bo'laklari va qiymatlarni alohida oladi (Tagged template literals). Teg ichidagi ' ham xavfsiz.
<{ id: bigint; matn: string }[]> — natija turini siz aytasiz. Prisma uni tekshirmaydi: xato tur yozsangiz, TypeScript ishonadi. Bu raw SQL'ning narxi (TypedSQL buni hal qiladi).
2.2 Natija turlari
Raw natijada Postgres turi JavaScript turiga Prisma qoidasi bilan aylanadi:
const [t] = await db.$queryRaw<Record<string, unknown>[]>`
SELECT id, count(*) OVER () AS jami,
count(*) OVER ()::int AS jami_int,
round(avg(izohlar_soni) OVER (), 2) AS ortacha,
yaratilgan_vaqt, jsonb_build_object('a', 1) AS j
FROM vazifalar WHERE egasi_id = 1 ORDER BY id LIMIT 1`;id: bigint "1n"
jami: bigint "13n"
jami_int: number 13
ortacha: Decimal 0.46
yaratilgan_vaqt: Date "2026-10-01T03:30:00.000Z"
j: object {"a":1}E'tibor bering: count(*) Postgres'da bigint — natija ham bigint (13n). JSON.stringify uni seriyalay olmaydi (Prisma Client CRUD). Eng oddiy davosi — SQL'da ::int. numeric — Prisma.Decimal (aniq kasr), timestamptz — Date, jsonb — obyekt.
Diqqat:
SELECT pg_sleep(0.1)ni$queryRawbilan bajarsak, xato chiqdi:Failed to deserialize column of type 'void'. Natija turi Prisma bilmaydigan (void) bo'lsa — uni tanlamang yoki::textga aylantiring. Natija kerak bo'lmasa —$executeRaw.
2.3 Nom — parametr emas
const ustun = "matn";
await db.$queryRaw`SELECT id FROM vazifalar
ORDER BY ${ustun} LIMIT 2`;
await db.$queryRaw`SELECT count(*) FROM ${"vazifalar"}`;xato yo'q, lekin: [{"id":"1n"},{"id":"2n"}]
PrismaClientKnownRequestError P2010 (asl 42601): Raw query failed. Code: `42601`. Message: `syntax error at or near "$1"`26-qismdagining aynan o'zi (SQL injection): ORDER BY $1 — doimiy qiymat bo'yicha "saralash" (umuman saralamaydi), jadval nomi o'rnida $1 — sintaksis xatosi. Raw SQL xatolari Prisma'da P2010 kodi bilan keladi.
Tekshirib ko'ring: Nega birinchi so'rov xato bermadi?
Javob
ORDER BY $1 — Postgres uchun to'g'ri SQL: "har qatorda bir xil 'matn' satri bo'yicha saralash". Hamma qatorda qiymat bir xil — tartib aniqlanmagan. Xato yo'q, natija noto'g'ri — eng yomon xato turi.
3. Bo'laklardan yig'ish
3.1 Prisma.sql va Prisma.join
Dinamik WHERE — 26-qismdagi Parametrlar klassining Prisma varianti. Har bo'lak — Prisma.sql (u ham tagged template), ularni Prisma.join birlashtiradi:
const shartlar = [
Prisma.sql`egasi_id = ${2}`,
Prisma.sql`bajarildi = ${false}`,
];
const idlar = [14n, 16n, 32n, 999n];
await db.$queryRaw`
SELECT id FROM vazifalar
WHERE ${Prisma.join(shartlar, " AND ")}
AND id IN (${Prisma.join(idlar)})
ORDER BY id`;[{"id":"14n"}]
SQL: SELECT id FROM vazifalar WHERE egasi_id = $1 AND bajarildi = $2 AND id IN ($3,$4,$5,$6) ORDER BY idParametrlar raqami o'zi to'g'ri qo'yildi: ichki bo'lakdagi ${2} ham $1 bo'ldi. Prisma.join(idlar) — vergul bilan, har biri parametr. Bo'sh massiv bersangiz — IN () sintaksis xatosi; bunday holatni oldindan tekshiring yoki = ANY(${idlar}::bigint[]) ishlating (massiv bitta parametr bo'ladi — kanon shunday qiladi).
Prisma.sql obyekti bajarilmasdan oldin nimaga aylanishini ko'rsa bo'ladi:
const s = Prisma.sql`SELECT id FROM vazifalar
WHERE egasi_id = ${3} AND matn ILIKE ${"%bozor%"}`;
console.log(s.text);
console.log(s.values);SELECT id FROM vazifalar WHERE egasi_id = $1 AND matn ILIKE $2
[ 3, '%bozor%' ](s.sql esa ? belgilari bilan — MySQL uslubi; Postgres uchun s.text.) Bu — 26-qismdagi { text, values } shaklining o'zi.
3.2 Prisma.raw — faqat oq ro'yxatdan
Ustun nomi yoki ASC/DESC kerak bo'lsa — Prisma.raw(matn). U matnni yopishtiradi — parametr emas. Shuning uchun faqat o'zimiz yozgan qiymat:
const SARALASH = {
yangi: "yaratilgan_vaqt DESC, id DESC",
alifbo: 'matn COLLATE "C", id',
} as const;
if (!Object.hasOwn(SARALASH, kalit)) {
throw new Error("noma'lum saralash");
}
const tartib = Prisma.raw(SARALASH[kalit as keyof typeof SARALASH]);
await db.$queryRaw`SELECT id FROM vazifalar
WHERE egasi_id = ${2} ORDER BY ${tartib} LIMIT 3`;[{"id":"16n"},{"id":"26n"},{"id":"29n"}]
SQL: SELECT id FROM vazifalar WHERE egasi_id = $1 ORDER BY matn COLLATE "C", id LIMIT 3COLLATE "C" — collation bo'yicha saralash, Prisma orderBy bera olmaydigan narsa. Object.hasOwn — "constructor" kabi prototip nomlaridan himoya.
flowchart LR
Q["Qiymat (id, matn, sana)"] --> P["${qiymat} → $1 parametr"]
L["Ro'yxat"] --> J["Prisma.join → $1,$2,$3"]
N["Nom (ustun, ASC/DESC)"] --> W{"Oq ro'yxatda?"}
W -- ha --> R["Prisma.raw"]
W -- yo'q --> X["xato"]
U["Foydalanuvchi matni"] -.-> Z["$queryRawUnsafe — HECH QACHON ulanmaydi"]Diagrammaga qarang: foydalanuvchidan kelgan narsa faqat yuqori uch yo'ldan o'tadi. Pastdagi punktir yo'l — injection.
4. $executeRaw — o'zgartirish, son qaytaradi
const n = await db.$executeRaw`
UPDATE vazifalar SET ustuvorlik = 'past'
WHERE egasi_id = ${2} AND bajarildi`;
console.log(n);3
SQL: UPDATE vazifalar SET ustuvorlik = 'past' WHERE egasi_id = $1 AND bajarildi$executeRaw — ta'sirlangan qatorlar soni. $queryRaw — qatorlar. SELECT … FOR UPDATE qulfi uchun ham $executeRaw qulay (natija kerak emas, faqat qulf) — kanondagi #yoz shunday.
5. $queryRawUnsafe — matn sifatida SQL
Unsafe metodlar SQL'ni oddiy satr sifatida oladi. Ular kerak bo'ladigan yagona holat — SQL matni oldindan butunlay ma'lum emas (masalan, fayldan o'qilgan migratsiya). Kanon va biz bir xil tajribani qildik:
const yomon = "1 OR 1=1"; // URL'dan kelgan "id"
// ❌ matn ulangan
await db.$queryRawUnsafe(
`SELECT id FROM vazifalar WHERE egasi_id = ${yomon}`,
);
// ✅ Unsafe, lekin qiymat $1 bilan
await db.$queryRawUnsafe(
"SELECT id FROM vazifalar WHERE egasi_id = $1", yomon,
);ulangan: 48 qator
$1 bilan: PrismaClientKnownRequestError P2010 (asl 22P02): Raw query failed. Code: `22P02`. Message: `invalid input syntax for type bigint: "1 OR 1=1"`
SQL: SELECT id FROM vazifalar WHERE egasi_id = 1 OR 1=1Birinchisida backtick — oddiy template literal ($queryRawUnsafe( dan keyin qavs bor — bu tag emas). Natija: egasi_id = 1 OR 1=1 — hamma 48 vazifa, 8 foydalanuvchiniki. Ikkinchisida qiymat parametr: Postgres "1 OR 1=1" ni son sifatida o'qishga urindi va rad etdi.
Farq bitta qavsda: $queryRaw`…${x}…` — xavfsiz; $queryRawUnsafe(`…${x}…`) — xavfli. Kod review'da Unsafe so'zi ko'rinsa — har doim to'xtab tekshiriladi.
Tekshirib ko'ring:
db.$queryRaw`SELECT … ORDER BY ${Prisma.raw(req.query.sort)}`xavflimi?
Javob
Ha, $queryRawUnsafe darajasida xavfli. Prisma.raw — matnni parametr qilmasdan yopishtiradi; sort ga id; DROP TABLE izohlar -- yozilsa, u SQL kodi bo'ladi. Prisma.raw faqat oq ro'yxatdagi o'zimiz yozgan qiymat bilan.
6. TypedSQL: .sql fayldan tipli funksiya
6.1 Muammo va g'oya
$queryRaw<T> da T ni qo'lda yozamiz — SQL o'zgarsa, tur eskirib qoladi. TypedSQL — SQL'ni alohida .sql faylga yozasiz, prisma generate --sql uni haqiqiy bazaga ulanib tahlil qiladi va parametr hamda natija turlari bilan TypeScript funksiya yasaydi.
Prisma 7.10 da TypedSQL hali preview (sinov bosqichidagi) imkoniyat — sxemada yoqiladi:
generator client {
provider = "prisma-client"
previewFeatures = ["partialIndexes", "typedSql"]
// … qolgani o'zgarmaydi
}(Bu schema.prisma bo'lagi; blok ts — chunki saytda prisma rangi yo'q.)
6.2 SQL fayl va generatsiya
prisma/sql/tegStatistikasi.sql — kanondagi teglar() so'rovi, parametr izohi bilan:
-- @param {BigInt} $1:egasiId
SELECT substr(t.nom, 2) AS teg, count(*)::int AS soni
FROM teglar t
JOIN vazifa_teglari vt ON vt.teg_id = t.id
JOIN vazifalar v ON v.id = vt.vazifa_id
WHERE t.egasi_id = $1 AND v.ochirilgan_vaqt IS NULL
GROUP BY t.nom
ORDER BY soni DESC, substr(t.nom, 2) COLLATE "C"-- @param {BigInt} $1:egasiId — $1 ning nomi va turi. Generatsiya (bazaga ulanadi — DATABASE_URL kerak):
DATABASE_URL=postgres://postgres@127.0.0.1:5432/vazifalar \
npx prisma generate --sqlPrisma yasagan fayldagi tur:
export namespace tegStatistikasi {
export type Parameters = [egasiId: number | bigint]
export type Result = {
teg: string | null
soni: number | null
}
}6.3 Ishlatish
import { tegStatistikasi }
from "./generated/prisma/sql/tegStatistikasi.ts";
const r = await db.$queryRawTyped(tegStatistikasi(3n));[{"teg":"ish","soni":2},{"teg":"mashina","soni":2},{"teg":"sport","soni":1},{"teg":"to'lov","soni":1}]
SQL: SELECT substr(t.nom, 2) AS teg, count(*)::int AS soni FROM teglar t JOIN … WHERE t.egasi_id = $1 AND … COLLATE "C"Endi xato ishlatish kompilyatsiyada ushlanadi. Biz tsc bilan tekshirdik:
const r = await db.$queryRawTyped(tegStatistikasi("3"));
const s: number = r[0].soni;TS2345: Argument of type 'string' is not assignable to parameter of type 'number | bigint'.
TS2322: Type 'number | null' is not assignable to type 'number'.
TS2532: Object is possibly 'undefined'.Ikkinchi xatoga qarang: soni — number | null. Postgres ifoda natijasi (count(*)::int) NULL bo'lishi mumkinmi — bilmaydi, shuning uchun TypedSQL ehtiyotkorlik bilan null qo'shadi. Kodda ?? 0 yoki tekshiruv kerak.
TypedSQL'ning cheklovlari (Prisma 7 hujjati bo'yicha, 2026-10-27 holatiga): preview; generatsiya uchun haqiqiy baza kerak (CI'da ham); dinamik SQL (o'zgaruvchan WHERE, Prisma.raw) — yo'q, fayl qat'iy. Kanon TypedSQL'ni sinamagan; yuqoridagi chiqishlar bizniki. Loyihaga hozircha kiritmadik: preview imkoniyat va CI'ga baza talabi — 33–34-qismda deploy qurilganda qayta ko'riladi.
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
Do not know how to serialize a BigInt |
count(*), bigint ustun |
SQL'da ::int yoki Number() |
P2010 syntax error at or near "$1" |
nom parametr qilindi | oq ro'yxat + Prisma.raw |
Failed to deserialize column of type 'void' |
pg_sleep kabi natija |
$executeRaw yoki ::text |
IN () sintaksis xatosi |
bo'sh Prisma.join |
oldindan tekshirish yoki = ANY |
| 48 qator 1 o'rniga | Unsafe ga matn ulangan |
tagged $queryRaw |
| tur noto'g'ri, xato yo'q | $queryRaw<T> da T taxmin |
TypedSQL yoki tekshiruv (Zod) |
8. Mashqlar
Mashqlar kurs/mashqlar/27/26-prisma-raw/ da. 2-mashq yechimi Node 24 da tekshirilgan (bazasiz); 3-mashqni biz bazada ishga tushirdik.
1-mashq (oson): xavfsizmi?
db.$queryRaw`SELECT * FROM vazifalar WHERE id = ${id}`—db.$queryRawUnsafe(`SELECT * FROM vazifalar WHERE id = ${id}`)—db.$queryRaw`… ORDER BY ${Prisma.raw(SARALASH[k])}`(khasOwnbilan tekshirilgan) —db.$queryRaw`… IN (${Prisma.join(idlar)})`—
Yechim
- xavfsiz — tag;
idparametr. - xavfli — oddiy template literal, matn ulanadi.
- xavfsiz — yopishtirilgan matn bizning oq ro'yxatdan.
- xavfsiz — har element parametr (bo'sh massivni alohida tekshiring).
2-mashq (o'rta): o'z sql tagingiz
Prisma.sql qanday ishlashini tushunish uchun kichik sql tag funksiyasini yozing: u { text, values } qaytarsin; har ${} — $1, $2…; agar qiymat ham shu tag natijasi bo'lsa (ichki bo'lak), uning matni joylanadi va qiymatlari davom ettirilgan raqamlar bilan qo'shiladi.
Yechim
const BOLAK = Symbol("sql");
function sql(qismlar, ...qiymatlar) {
const bolaklar = [];
const values = [];
qismlar.forEach((q, i) => {
bolaklar.push({ matn: q });
if (i < qiymatlar.length) bolaklar.push({ qiymat: qiymatlar[i] });
});
return { [BOLAK]: true, bolaklar, values };
}
function yasash(s, values = []) {
let text = "";
for (const b of s.bolaklar) {
if ("matn" in b) text += b.matn;
else if (b.qiymat?.[BOLAK]) text += yasash(b.qiymat, values).text;
else {
values.push(b.qiymat);
text += `$${values.length}`;
}
}
return { text, values };
}
const shart = sql`egasi_id = ${2} AND bajarildi = ${false}`;
const hujum = "' OR 1=1 --";
const q = yasash(
sql`SELECT id FROM vazifalar WHERE ${shart} AND matn = ${hujum}`,
);
console.log(q.text);
console.log(q.values);Konsolda:
SELECT id FROM vazifalar WHERE egasi_id = $1 AND bajarildi = $2 AND matn = $3
[ 2, false, "' OR 1=1 --" ]Tag funksiya matn bo'laklarini (qismlar) va qiymatlarni alohida oladi — shuning uchun qiymat hech qachon kodga aylanmaydi. Ichki bo'lak (shart) raqamlashni buzmadi: $1, $2, keyin tashqi $3. Prisma'ning Prisma.sql i shu g'oyaning to'liq (va sinalgan) varianti.
3-mashq (qiyin): TypedSQL bilan statistika
prisma/sql/ustuvorlikStatistikasi.sql yozing: berilgan egasining o'chirilmagan vazifalari ustuvorlik bo'yicha — jami va bajarilgan (count(*) FILTER (WHERE …)). Generatsiya qiling va Aziz (1) uchun chiqaring.
Yechim
-- @param {BigInt} $1:egasiId
SELECT ustuvorlik::text AS ustuvorlik,
count(*)::int AS jami,
count(*) FILTER (WHERE bajarildi)::int AS bajarilgan
FROM vazifalar
WHERE egasi_id = $1 AND ochirilgan_vaqt IS NULL
GROUP BY ustuvorlik
ORDER BY ustuvorlikimport { ustuvorlikStatistikasi }
from "./generated/prisma/sql/ustuvorlikStatistikasi.ts";
const qatorlar = await db.$queryRawTyped(ustuvorlikStatistikasi(1n));
for (const q of qatorlar) {
console.log(q.ustuvorlik, q.jami, q.bajarilgan);
}Biz ishga tushirdik (PostgreSQL 17.6, Prisma 7.10.1):
orta 8 4
past 1 0
yuqori 3 18 + 1 + 3 = 12 — Azizning 13 vazifasidan biri Savatda. Bir nozik joy: ORDER BY ustuvorlik bu yerda ustuvorlik::text AS ustuvorlik taxallusiga ishora qiladi — alifbo tartibi (orta, past, yuqori). Enum tartibi (past, orta, yuqori) kerak bo'lsa, ORDER BY vazifalar.ustuvorlik yozing. Generatsiya qilingan turda uchala maydon | null — GROUP BY natijasini Postgres "NULL bo'lmaydi" deb kafolatlamaydi.
9. Loyiha qadami: PrismaOmbori yakuni va holat 20 commit
Oldingi uch darsda PrismaOmbori ning klienti, ol, royxat, soni, yarat, yangila, ochir metodlari yozildi. Prisma Client yetmagan uch joy raw SQL bilan tugaydi.
9.1 teglar() — uch jadval bo'yicha guruhlash
async teglar(): Promise<TegSoni[]> {
// groupBy relation orqali guruhlay olmaydi — raw SQL
return this.#db.$queryRaw<TegSoni[]>`
SELECT substr(t.nom, 2) AS teg, count(*)::int AS soni
FROM teglar t
JOIN vazifa_teglari vt ON vt.teg_id = t.id
JOIN vazifalar v ON v.id = vt.vazifa_id
WHERE t.egasi_id = ${this.#egasi} AND v.ochirilgan_vaqt IS NULL
GROUP BY t.nom
ORDER BY soni DESC, substr(t.nom, 2) COLLATE "C"`;
}count(*)::int — bigint emas, number kelsin. Kanon o'lchovida bu metod pg'nikidan biroz tez chiqdi: 5.49 ms (pg) / 5.00 ms (prisma) — SQL bir xil.
9.2 Matn bo'yicha saralash — COLLATE "C"
Ro'yxat ?sort=matn bilan so'ralganda: mos id'larni Prisma filtri bilan, tartibni raw SQL bilan olamiz. Saralash ifodasi — oq ro'yxatdan:
const MAYDON_SQL = {
id: "id", tartib: "tartib", matn: 'matn COLLATE "C"',
bajarildi: "bajarildi", yaratildi: "yaratilgan_vaqt",
yangilandi: "yangilangan_vaqt",
} as const;
/** Ustun ifodasi — faqat oq ro'yxatdan */
function ustunSql(maydon: Saralash["maydon"]): string {
if (!Object.hasOwn(MAYDON_SQL, maydon)) {
throw new Error(`saralash maydoni oq ro'yxatda yo'q: ${maydon}`);
}
return MAYDON_SQL[maydon];
}royxat ichidagi raw tarmoq:
const mos = await this.#db.vazifa.findMany({
where: { AND: asos }, select: { id: true },
});
const tartib = Prisma.raw(k.map((s) =>
`${ustunSql(s.maydon)} ${s.kamayish ? "DESC" : "ASC"}`).join(", "));
qatorlar = await this.#db.$queryRaw<VazifaQatori[]>`
SELECT id, matn, bajarildi, tartib, versiya,
yaratilgan_vaqt AS "yaratilganVaqt",
yangilangan_vaqt AS "yangilanganVaqt"
FROM vazifalar
WHERE id = ANY(${mos.map((m) => m.id)}::bigint[]) AND ${kursor}
ORDER BY ${tartib} LIMIT ${olish} OFFSET ${otkaz}`;Uch narsa: = ANY(${massiv}::bigint[]) — ro'yxat bitta parametr (bo'sh bo'lsa ham xato yo'q); AS "yaratilganVaqt" — natija maydon nomlari Prisma qatori bilan bir xil bo'lsin; ${kursor} — keysetSql yasagan Prisma.sql bo'lagi (ichida ham qiymatlar parametr, ::bigint/::text turlari bilan). Kanon o'lchovida aynan shu yo'l eng qimmat: matn saralash pg 5.42 ms, prisma 12.07 ms — ikki so'rov.
9.3 almashtirHammasi — unnest
PUT /v1/vazifalar butun ro'yxatni almashtiradi: har vazifaga har xil matn va tartib. Prisma updateMany hammaga bir xil qiymat beradi, createMany esa OVERRIDING SYSTEM VALUE (o'z id'ini berish) bilmaydi. Kanon 26-qismdagi unnest naqshini (Ommaviy ma'lumot: COPY, batch va cursor) Prisma tranzaksiyasi ichida ishlatadi:
await tx.$executeRaw`
UPDATE vazifalar v SET matn = u.matn, bajarildi = u.bajarildi,
bajarilgan_vaqt = CASE WHEN u.bajarildi
THEN coalesce(v.bajarilgan_vaqt, now()) END,
tartib = u.tartib, ochirilgan_vaqt = NULL
FROM unnest(${u.id}::bigint[], ${u.matn}::text[],
${u.bajarildi}::boolean[], ${u.tartib}::int[])
AS u(id, matn, bajarildi, tartib)
WHERE v.id = u.id AND v.egasi_id = ${this.#egasi}`;To'rt massiv — to'rt parametr, qatorlar soni qancha bo'lmasin. Shundan keyin yangi qatorlar xuddi shunday INSERT … OVERRIDING SYSTEM VALUE SELECT … FROM unnest(…) bilan, oxirida setval bilan sequence tuzatiladi. Kanon o'lchovi: 1 000 qatorli PUT — pg 168 ms, prisma 210 ms.
9.4 Testlar va tekshiruv
test/prisma-ombor.test.ts — 25-qismdagi shartnoma testlari (omborShartnomasi("prisma", …)) va PgOmbori ning Postgres'ga xos testlari, endi Prisma ombori uchun. Baza — alohida vazifalar_test_prisma: node --test fayllarni parallel jarayonlarda ishga tushiradi va ikkala test fayli bitta bazani TRUNCATE qilsa, bir-birining ma'lumotini o'chiradi (kanon shunday 2 ta yiqilgan testni topgan — Test DB strategiyalari darsida batafsil).
Injection testidan bir parcha:
const r = (qidiruv: string) => a.royxat({
filtr: { qidiruv }, saralash: [], limit: 10,
sahifa: { tur: "kursor" },
});
assert.deepEqual((await r("%")).vazifalar.map((v) => v.id), [1]);
assert.deepEqual((await r("_")).vazifalar.map((v) => v.id), []);
assert.deepEqual(
(await r("' OR 1=1 --")).vazifalar.map((v) => v.id), [],
);% — faqat matnida haqiqatan % bor vazifa (Azizniki 100% #ish) — filtrlash darsidagi ekranlash shu testda tug'ilgan.
Biz holat 20 ni o'z klasterimizda (PostgreSQL 17.6, ikkala test bazasi noldan) tekshirdik:
npm run tip && npm run lint
B=postgres://postgres@127.0.0.1:5432
TEST_DATABASE_URL=$B/vazifalar_test \
TEST_PRISMA_DATABASE_URL=$B/vazifalar_test_prisma \
npm testChiqish uzun (Prisma qismi). Nimaga qarang: har ✔ — bitta talab, oxirgi uch qator — jami natija:
▶ VazifaOmbori shartnomasi: prisma
✔ yarat: id 1 dan, teglar matndan, versiya 1, tartib oxiriga, sanalar ISO UTC; ol — nusxa (3478.3565ms)
✔ yangila: versiya +1, shart mos kelmasa konflikt, yo'q id — topilmadi (726.4868ms)
✔ ochir: shart, topilmadi, to'plam versiyasi har yozuvda oshadi (344.4134ms)
✔ royxat: filtr (bajarildi, teglar AND, qidiruv), saralash, offset, kursor, jami; soni; teglar (833.5219ms)
✔ almashtirHammasi: massiv tartibi = tartib (sukut saralash), id saqlanadi/beriladi, eski o'chadi (552.0879ms)
▶ PrismaOmbori: Postgres'ga xos (PgOmbori testlari bilan bir xil)
✔ takror matn — 409 (23P01 adapter xatosidan); o'chirilgandan keyin mumkin; id sequence'ni yeydi (1335.6363ms)
✔ almashtirHammasi atomik: takror matn — 409, eski ro'yxat va versiya o'zgarmaydi; matnlarni almashtirish (014) (655.5209ms)
✔ egasi ajratilgan va injection: qidiruv '%' / "' OR 1=1 --" — oddiy matn; saralash maydoni — oq ro'yxat (1304.8333ms)
✔ matn bo'yicha kursor — COLLATE "C" (JS tartibi): katta harf kichikdan oldin, takrorsiz (308.8715ms)
✔ raqobat: 30 parallel yarat — tartib takrorlanmaydi; bitta versiya bilan 10 PATCH — 1 ok (1107.6805ms)
✔ omborYarat(prisma): SAQLASH=prisma — fail-fast va ishlaydi (2951.787ms)
ℹ tests 99
ℹ pass 99
ℹ fail 0Nima bo'ldi? tip va lint — 0 xato, testlar 99/99: shartnoma testlari ikkala omborga bir xil talab qo'ydi va Prisma ombori hammasidan o'tdi. Kanon (PostgreSQL 18.6) ham 99/99 oldi, uch marta ketma-ket; jonli serverda SAQLASH=postgres va SAQLASH=prisma ikkalasi 9 ta curl so'roviga bir xil HTTP kodlar qaytardi.
9.5 Commit
git add src/vazifalar/prisma-ombor.ts src/ishga-tushir.ts \
src/config.ts prisma/ prisma.config.ts \
test/prisma-ombor.test.ts package.json package-lock.json \
.gitignore eslint.config.js tsconfig.json
M='feat: Prisma 7 ombori (SAQLASH=prisma) — prisma-client '
M+='generatori, adapter-pg, shartnoma testlari'
git commit -m "$M"src/generated/ git'ga kirmaydi (.gitignore): uni postinstall: prisma generate har npm ci da qayta yasaydi.
10. Real ishda
- Deyarli har Prisma loyihasida bir nechta raw SQL bor: hisobotlar,
FOR UPDATE, full-text search, ommaviy yangilash. Ularni bitta faylda (ombor) saqlash va testlash odat. - Kod review qoidasi:
UnsafevaPrisma.raw— har biri izoh bilan, nega xavfsiz ekani yozilgan bo'lsin. - Intervyuda: "
$queryRawva$queryRawUnsafefarqi?", "Tagged template injection'dan qanday himoya qiladi?", "ORM bilan raw SQL'ni qachon ishlatasiz?"
Xulosa
$queryRaw`…${x}…`—xparametr bo'ladi;$executeRaw— o'zgartirish, son qaytaradi; xatolar — P2010.- Bo'laklar —
Prisma.sql, ro'yxat —Prisma.joinyoki= ANY(…::bigint[]), nom — oq ro'yxat +Prisma.raw. $queryRawUnsafega matn ulash — injection: 1 o'rniga 48 qator.- Raw natijada
count(*)—bigint,numeric—Decimal;::intbilan oldini oling. - TypedSQL (preview):
.sqlfayl → tipli funksiya; natija maydonlari ko'pincha| null. - Holat 20 tayyor:
PrismaOmbori99/99 testdan o'tdi va commit qilindi.
Keyingi dars: Prisma Client extensions — $extends bilan hisoblangan maydon, model metodi, soft delete va tenant filtri — va ularning teshiklari.
Manbalar
- Prisma hujjati — Raw queries: https://www.prisma.io/docs/orm/v7/prisma-client/using-raw-sql/raw-queries (2026-10-25)
- Prisma hujjati — TypedSQL: https://www.prisma.io/docs/orm/v7/prisma-client/using-raw-sql/typedsql
- Prisma hujjati — Error reference (P2010): https://www.prisma.io/docs/orm/v7/reference/error-reference
- Chiqishlar — biz ishga tushirdik: PostgreSQL 17.6, Prisma 7.10.1, Node 24.21.0 (2026-10-25/26); o'lchovlar va jonli tekshiruv — kanon (PostgreSQL 18.6).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!