Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. O'rnatish va birinchi so'rov
- 2.1 Tajriba papkasi
- 2.2 Client: bitta ulanish
- 2.3 Ulanish xatolari
- 3. Natija obyekti
- 3.1 Qatorlar massiv ko'rinishida
- 4. Pool: ulanishlar hovuzi
- 4.1 Nega Client serverga yetmaydi
- 4.2 pool.query — o'zi oladi, o'zi qaytaradi
- 4.3 Qaysi biri qachon
- 5. Turlar: Postgres → JavaScript
- 5.1 Kanon jadvali
- 5.2 bigint va count(*) nega string
- 5.3 numeric nega string
- 5.4 date tuzog'i: bir kun oldin
- 5.5 setTypeParser: o'giruvchini o'zgartirish
- 6. Xato obyekti: code va constraint
- 7. SSL va xavfsiz ulanish
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): xavfsiz bigint o'giruvchisi
- 3-mashq (qiyin): foydalanuvchi statistikasi
- 10. Node qadami: vazifalar-api Postgres'ga ulanadi
- 10.1 Sxema: 013-api-tartib.sql
- 10.2 pg va PgOmbori ning o'qish qismi
- 10.3 Shartnoma testlari
- 11. Real ishda
- Xulosa
- Manbalar
node-postgres (pg) asoslari: Client, Pool, natija obyekti va turlar
Qisqacha: Node ilovasi Postgres bilan drayver orqali gaplashadi; eng keng tarqalgani —
pg(node-postgres).new pg.Pool({ connectionString })ulanishlar hovuzini yaratadi,pool.query(sql, [qiymatlar])esa natija obyekti qaytaradi:rows— qatorlar massivi,rowCount— nechta qator. Turlar avtomatik o'giriladi, lekinbigint,count(*)vanumericstring bo'lib keladi,dateesa mahalliy vaqt zonasidagiDatebo'ladi — bu ikki tuzoqni kanon tajribasi ko'rsatdi.
Bu darsda
pgni o'rnatib, birinchi so'rovni Node'dan yuborasiz.ClientvaPoolfarqini va qachon qaysi biri kerakligini tushuntirasiz.- Natija obyektini o'qiysiz:
rows,rowCount,command,fields. - Postgres turlari JavaScript'ga qanday o'girilishini bilasiz va
setTypeParserbilan sozlaysiz. - Xato obyektidagi
codevaconstraintbo'yicha ilovada qaror qabul qilasiz.
Oldin bilishingiz kerak: SELECT va WHERE, PostgreSQL o'rnatish va ulanish, PostgreSQL ma'lumot turlari, Express'ga kirish, --watch, --env-file va node --run.
1. Nega bu kerak?
«SQL qadami» bo'limlarida biz vazifalar bazasini psql orqali qurdik: 6 jadval, view, trigger'lar, 48 ta namuna vazifa. psql — inson uchun qulay vosita. Lekin 25-qismdagi vazifalar-api serveri inson emas. U har HTTP so'rovda bazaga o'zi murojaat qilishi kerak: "Azizning vazifalarini ber", "yangi vazifa qo'sh".
Node'ning o'zida Postgres bilan gaplashadigan modul yo'q. Bu ishni drayver (driver) bajaradi — dastur bilan baza orasidagi tarjimon kutubxona. Drayver Postgres'ning tarmoq protokolini biladi: TCP ulanish ochadi, so'rovni baytlarga o'giradi, javobni JavaScript obyektlariga aylantiradi.
O'xshatish: chet eldagi do'konga buyurtma beryapsiz. Siz o'zbekcha yozasiz, tarjimon xatni ularning tiliga o'giradi va javobni sizga o'zbekcha qaytaradi. Siz protokol (ularning tili) bilan ishlamaysiz — faqat "nima kerak" deysiz.
sequenceDiagram
participant A as Node ilova
participant D as pg (drayver)
participant P as Postgres
A->>D: pool.query(sql, [1])
D->>P: TCP: so'rov baytlari
P-->>D: qatorlar (matn ko'rinishida)
D-->>A: { rows: [...], rowCount }Diagrammaga qarang: Postgres javobni matn sifatida yuboradi ('48', 't', '2026-10-24'). Uni number, boolean, Date ga aylantirish — drayverning ishi. Darsning eng muhim tuzoqlari aynan shu o'girishda.
Bu qism kanoni (biz o'lchagan muhit): PostgreSQL 18.6 (Docker), Node 24.21.0, pg 8.23.1, Windows 11. Kanon chiqishlari — natijalar/tajriba/t41-pg-asoslari.txt faylidan.
2. O'rnatish va birinchi so'rov
2.1 Tajriba papkasi
Node qadamidagi tajribalar uchun alohida papka oching (vazifalar-api ga hali tegmaymiz — sababi «Node qadami» bo'limida):
mkdir pg-tajriba
cd pg-tajriba
npm init -y
npm i pg@8.23.1package.json ga "type": "module" qo'shing — ESM va top-level await uchun (ES Modules Node'da). Ulanish manzilini kodga yozmaymiz — .env faylga:
DATABASE_URL=postgres://postgres@127.0.0.1:5499/vazifalarBu PostgreSQL o'rnatish va ulanish darsidagi kanon konteyneri: lokal, parolsiz (trust). Haqiqiy serverda manzilda parol bo'ladi — shuning uchun u kodda emas, muhit o'zgaruvchisida turadi. Skriptni node --env-file=.env fayl.js bilan ishga tushiramiz (--env-file).
Diqqat: Parolli
DATABASE_URLni hech qachon kodga va git'ga yozmang..env—.gitignoreda, repoda faqat.env.example(parolsiz namuna).
2.2 Client: bitta ulanish
import pg from "pg";
const client = new pg.Client({
connectionString: process.env.DATABASE_URL,
});
await client.connect(); // TCP ulanish ochiladi
const r = await client.query(
"SELECT id, matn, bajarildi FROM vazifalar " +
"WHERE egasi_id = $1 ORDER BY id LIMIT 2",
[1],
);
console.log("rows:", r.rows);
await client.end(); // ulanish yopiladiKanon chiqishi:
rows: [
{
id: '1',
matn: "JavaScript kursining 26-qismini boshlash #o'qish",
bajarildi: false
},
{
id: '2',
matn: "Postgres'ni Docker'da ko'tarish #o'qish #docker",
bajarildi: true
}
]Uch narsaga qarang:
import pg from "pg"—pgCommonJS paket, shuning uchun default import qilib,pg.Clientdeb olamiz.$1— parametr: qiymat (1) SQL matniga yopishtirilmaydi, alohida massivda ketadi. Nega bu hayot-mamot masalasi — keyingi darsda. Hozircha qoida: foydalanuvchidan kelgan har qiymat —$1, $2, …orqali.id: '1'— qo'shtirnoqda!idustunibigint,pguni string qilib berdi.bajarildiesa haqiqiyfalse(boolean). Sababini «Turlar» bo'limida ko'ramiz.
client.end() ni unutsangiz, ochiq TCP ulanish event loop'ni band qiladi va skript tugamaydi — terminal "osilib" qoladi (Event loop fazalari).
2.3 Ulanish xatolari
Server ishlamasa yoki baza nomi xato bo'lsa, connect() xato tashlaydi. Biz ishga tushirdik (PostgreSQL 17.6, Node 24.21.0, Windows 11):
ECONNREFUSED | connect ECONNREFUSED 127.0.0.1:5490
3D000 | database "yoq_baza" does not existBirinchisi — Node'ning tarmoq xatosi (ECONNREFUSED — "ulanish rad etildi": bu portda hech kim tinglamayapti; Docker konteyneri to'xtagan bo'lishi mumkin). Ikkinchisi — Postgres'ning o'z javobi: 3D000 — SQLSTATE kodi, "bunday baza yo'q". Postgres xatolarida doim besh belgili kod bor — u «Xato obyekti» bo'limida asosiy vositamiz bo'ladi.
Tekshirib ko'ring: Skript natijani to'g'ri chiqardi, lekin terminalga qaytmayapti. Nima unutilgan?
Javob
await client.end(). Ochiq ulanish (soket) event loop'da faol resurs — u yopilmaguncha Node jarayonni tugatmaydi. Serverda bu muammo emas (server baribir ishlab turadi), lekin skript va testlarda har doim yoping.
3. Natija obyekti
query() faqat qatorlarni emas, butun obyektni qaytaradi. Kanon chiqishidan (SELECT va UPDATE):
rowCount: 2 | command: SELECT | fields: id(oid 20), matn(oid 25), bajarildi(oid 16)
UPDATE rowCount: 13 | rows: 0| Xususiyat | Nima | Misol |
|---|---|---|
rows |
qatorlar massivi (har biri obyekt) | [{ id: '1', … }] |
rowCount |
qaytgan yoki o'zgargan qatorlar soni | 2, UPDATE da 13 |
command |
bajarilgan buyruq turi | SELECT, UPDATE |
fields |
ustunlar: nomi va tur raqami | id(oid 20) |
UPDATE … WHERE egasi_id = 1 uchun rows bo'sh, rowCount esa 13 — Azizning 13 ta vazifasi yangilandi. Bu juda foydali: API'da "id = 999 ni yangila" so'rovi rowCount: 0 qaytarsa — bunday vazifa yo'q, javob 404. RETURNING bilan (INSERT va RETURNING) esa UPDATE ham rows qaytaradi.
OID (object identifier) — Postgres har turga bergan raqam: 20 — bigint, 25 — text, 16 — boolean, 1082 — date. Drayver turlarni aynan shu raqam bo'yicha o'giradi. Bu raqamlarni yodlash shart emas, lekin 20 va 1082 ni eslab qoling — pastda ular kerak bo'ladi.
3.1 Qatorlar massiv ko'rinishida
Ba'zan obyekt emas, massiv qulay (masalan CSV yozganda). rowMode: "array":
const r3 = await pool.query({
text: "SELECT id, matn FROM vazifalar WHERE id = $1",
values: [2],
rowMode: "array",
});
console.log("rowMode array:", r3.rows);Kanon chiqishi:
rowMode array: [ [ 2, "Postgres'ni Docker'da ko'tarish #o'qish #docker" ] ]Bu yerda id — son 2, string emas. Sababi: kanon skripti bu so'rovdan oldin bigint uchun o'z o'giruvchisini o'rnatgan («Turlar» bo'limiga qarang).
Yana bir shakl — nomlangan so'rov (name): { name: "vazifa-ol", text, values }. Postgres so'rovni birinchi marta tahlil qilib eslab qoladi (prepared statement), shu ulanishda keyingi safar tahlilni takrorlamaydi. Kanon chiqishi: named statement: { matn: 'Mijozga taklifnoma yuborish #ish' }. Odatda kerak emas — pg oddiy parametrli so'rovni ham tez bajaradi; tashqi pooler bilan esa nomlangan so'rov muammo tug'dirishi mumkin (PgBouncer).
4. Pool: ulanishlar hovuzi
4.1 Nega Client serverga yetmaydi
Ulanish ochish qimmat: TCP, autentifikatsiya, Postgres serverida yangi jarayon. Kanon o'lchovi (Connection pooling darsida batafsil): har so'rovga yangi ulanish — 12.7 ms, tayyor ulanishdan — 1.1 ms. Bitta umumiy Client esa boshqa muammo: bir ulanish bir vaqtda faqat bitta so'rov bajaradi, qolganlari navbatda turadi.
Yechim — hovuz (pool): bir nechta ochiq ulanish, so'rov kelganda bo'shini beradi, ish tugagach qaytarib oladi. Xuddi taksi parkiga o'xshaydi: har yo'lovchi uchun yangi mashina sotib olinmaydi — bo'sh turgan mashina yuboriladi, safardan keyin u parkka qaytadi.
flowchart LR
S1[so'rov 1] --> P{Pool}
S2[so'rov 2] --> P
S3[so'rov 3] --> P
P --> U1[ulanish A]
P --> U2[ulanish B]
P -.navbat.-> S3
U1 --> DB[(Postgres)]
U2 --> DBDiagrammada ikkita ulanish bor: uchinchi so'rov bo'sh ulanish chiqquncha navbatda kutadi.
4.2 pool.query — o'zi oladi, o'zi qaytaradi
import pg from "pg";
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
max: 5, // ko'pi bilan 5 ta ulanish
});
const ishlar = Array.from({ length: 5 }, () =>
pool.query("SELECT pg_backend_pid() AS pid, pg_sleep(0.05)"),
);
const natijalar = await Promise.all(ishlar);
const pidlar = new Set(natijalar.map((x) => x.rows[0].pid));
console.log("turli ulanishlar:", pidlar.size);
await pool.end();Kanon chiqishi:
5 parallel so'rov, turli ulanishlar: 5 | pool: { total: 5, idle: 5, waiting: 0 }pg_backend_pid() — so'rovni bajargan server jarayoni raqami. Beshta parallel so'rov beshta turli jarayonda bajarildi: hovuz 5 ta ulanish ochdi. Ish tugagach hammasi bo'sh (idle: 5) — keyingi so'rovlarni kutyapti.
pool.query() ichida uch qadam bor: bo'sh ulanishni oladi → so'rovni bajaradi → ulanishni hovuzga qaytaradi. Siz hech narsa qaytarmaysiz — shuning uchun oddiy so'rovlar uchun eng xavfsiz yo'l shu.
4.3 Qaysi biri qachon
| Holat | Tanlov |
|---|---|
| Server, API — ko'p parallel so'rov | Pool (ilovada bitta, modul darajasida) |
| Bir martalik skript, migratsiya | Client yoki Pool — farqi kam |
| Tranzaksiya (bir nechta so'rov bitta ulanishda) | pool.connect() → client → release() |
LISTEN — doimiy tinglash |
alohida Client |
Uchinchi qator — Tranzaksiya asoslari darsida, to'rtinchisi — LISTEN/NOTIFY darsida. Eng ko'p uchraydigan xato: har HTTP so'rovda new pg.Pool() yaratish. Hovuz bir marta, ilova ishga tushganda yaratiladi va hamma joyda ishlatiladi.
Tekshirib ko'ring:
max: 5li hovuzga bir vaqtda 8 tapool.querykeldi. Nima bo'ladi?
Javob
5 tasi darhol ulanish oladi, 3 tasi navbatda kutadi (waitingCount: 3). Birinchi ulanish bo'shashi bilan navbatdagisi uni oladi. Xato yo'q — faqat kutish. Kutish chegarasi (connectionTimeoutMillis) qo'yilsa, juda uzoq kutgan so'rov xato bilan tugaydi (Connection pooling).
5. Turlar: Postgres → JavaScript
5.1 Kanon jadvali
Kanon bitta so'rovda 16 ta turli turdagi qiymatni oldi va JavaScript'da qaysi turga aylanganini chiqardi:
int number 1
bigint string "9007199254740993"
count string "48"
numeric string "12.50"
float8 number 0.1
bool boolean true
tz Date 2026-10-24T04:00:00.000Z
ts Date 2026-10-24T04:00:00.000Z
date Date 2026-10-23T19:00:00.000Z
interval object {"days":1,"hours":2}
jsonb object {"a":1}
int_massiv Array [1,2]
text_massiv Array ["#uy"]
enum string "yuqori"
uuid string "36188893-7917-44ed-91b2-ed6d3a00d863"
bytea Buffer {"type":"Buffer","data":[1,2]}Ko'pchiligi kutilganidek: int → son, jsonb → obyekt, massiv → Array, timestamptz → Date. Lekin uchta joy alohida e'tibor talab qiladi.
5.2 bigint va count(*) nega string
JavaScript'ning number turi butun sonni faqat 2⁵³ − 1 gacha aniq saqlaydi (Number.MAX_SAFE_INTEGER). Postgres bigint esa 2⁶³ − 1 gacha boradi. Kattaroq sonni number ga aylantirsak — jim buziladi:
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number("9007199254740993")); // 9007199254740992
console.log(BigInt("9007199254740993")); // 9007199254740993nIkkinchi qatorga qarang: oxirgi raqam 3 emas, 2 bo'ldi. Hech qanday xato yo'q — shunchaki boshqa son. ID'lar uchun bu dahshat: 993-vazifani so'rasangiz, 992-ni olasiz. Shuning uchun pg xavfsiz yo'lni tanlaydi: bigint (OID 20) ni string qiladi. count(*) ham bigint qaytaradi — shuning uchun "48" string.
Bizning vazifalar sxemamizda hamma ID bigint (Primary key tanlovi). Demak har id string keladi.
5.3 numeric nega string
numeric(12,2) — aniq o'nlik son, pul uchun (Pul, miqdor va enum'larni modellash). JavaScript'da aniq o'nlik tur yo'q — number ikkilik kasr, 0.1 + 0.2 bo'lmaydi 0.3. pg aniqlikni yo'qotmaslik uchun "12.50" ni string qoldiradi. Hisoblashni bazada qiling yoki tiyinda butun son bilan ishlang.
5.4 date tuzog'i: bir kun oldin
Eng ayyor tuzoq. '2026-10-24'::date — sana, vaqtsiz. Kanon natijasi:
date Date 2026-10-23T19:00:00.000Z24-oktabr 23-oktabr bo'lib ketdi! Sabab: pg sanani JavaScript Date ga Node jarayonining mahalliy vaqt zonasida yarim tun deb o'giradi. Toshkent — UTC+5, demak 24-oktabr 00:00 (Toshkent) = 23-oktabr 19:00 (UTC). toISOString() UTC'da ko'rsatadi — va sana surildi. Serverdagi Node UTC'da, sizniki Toshkentda bo'lsa, bir xil kod turli natija beradi.
Zonasiz timestamp (ts qatori) ham xuddi shunday: 09:00 mahalliy deb olindi → 04:00Z. Faqat timestamptz (zonali) ishonchli — u aniq vaqt nuqtasi. Bizning sxemada hamma vaqt ustunlari timestamptz (SQL'da sana va vaqt zonalari) — shuning uchun bu tuzoq faqat date ga tegishli.
5.5 setTypeParser: o'giruvchini o'zgartirish
pg.types.setTypeParser(oid, funksiya) — shu OID uchun o'z o'giruvchingiz. Funksiya Postgres yuborgan matnni oladi. Kanon tavsiyasi ikkita:
import pg from "pg";
// bigint (OID 20) → number, lekin xavfsiz bo'lmasa xato
pg.types.setTypeParser(20, (s) => {
const n = Number(s);
if (!Number.isSafeInteger(n)) {
throw new RangeError(`bigint ${s} Number'ga sig'maydi`);
}
return n;
});
// date (OID 1082) → o'girmasdan, "2026-10-24" satri
pg.types.setTypeParser(1082, (s) => s);Kanon chiqishi (bigint o'giruvchisidan keyin):
setTypeParser(20) dan keyin: { id: 1, jami: 13 } number
katta bigint: RangeError bigint 9007199254740993 Number'ga sig'maydidate o'giruvchisini biz ishga tushirdik (PostgreSQL 17.6, Node 24.21.0, TZ=Asia/Tashkent):
sukut: 2026-10-23T19:00:00.000Z
parser bilan: 2026-10-24Nega bigint uchun shunchaki Number emas, tekshiruvli funksiya? Chunki jim buzilish ochiq xatodan yomonroq. Bizning ilovada ID 9 kvadrillionga yetmaydi — lekin yetsa, o'giruvchi darhol "baqiradi". BigInt qaytarish ham mumkin, lekin JSON.stringify uni bilmaydi:
const vazifa = { id: 1n, matn: "Bozordan meva olish" };
console.log(JSON.stringify(vazifa));Konsolda:
TypeError: Do not know how to serialize a BigInt"BigInt'ni qanday seriyalashni bilmayman" — API javobi JSON bo'lgani uchun BigInt har javobda qo'shimcha ish talab qiladi. Kanon tanlovi — xavfsiz number.
Diqqat:
setTypeParser— global sozlama: butun jarayondagi hammapgulanishlariga ta'sir qiladi. Uni ilova boshida, bitta joyda (masalandb.jsmodulida) chaqiring. Undan ham xavfsizi — o'giruvchini faqat bitta hovuzga berish (pg.TypeOverrides);vazifalar-apiaynan shunday qiladi («Node qadami» bo'limi).
Tekshirib ko'ring:
SELECT count(*) FROM vazifalarnatijasiniif (r.rows[0].count > 10)bilan tekshirdingiz. Bu ishlaydimi?
Javob
Tasodifan ishlaydi: "48" > 10 da JavaScript satrni songa aylantiradi. Lekin r.rows[0].count + 1 → "481" (satr ulanishi!). Ishonchli yo'l — SQL'da count(*)::int deb yozish (Postgres integer qaytaradi → number) yoki Number(...) bilan aniq o'girish.
6. Xato obyekti: code va constraint
Bazadagi cheklovlar (CREATE TABLE va cheklovlar) buzilsa, pg xato tashlaydi. Xato obyektida Postgres yuborgan maydonlar bor. Kanon chiqishi:
xato: {
code: '23505',
constraint: 'vazifalar_egasi_id_matn_key',
table: 'vazifalar',
detail: 'Key (egasi_id, lower(matn))=(1, onamga dori olib borish #oila) already exists.'
}
xato: 23514 vazifalar_matn_check
xato: 22P02 invalid input syntax for type integer: "abc"Birinchisi — Aziz "Onamga dori olib borish #OILA" ni qo'shmoqchi bo'ldi, lekin registrga befarq noyoblik cheklovi bor: "kalit (egasi_id, lower(matn)) allaqachon mavjud". Ikkinchisi — bo'sh matn, CHECK cheklovi. Uchinchisi — "abc" ni songa aylantirib bo'lmadi.
Ilova xato matnini emas, code ni tekshiradi — matn til va versiyaga qarab o'zgaradi, kod esa doimiy. API'da bu HTTP javobga aylanadi (Xato javoblari: Problem Details):
| SQLSTATE | Ma'nosi | HTTP |
|---|---|---|
23505 |
unique buzildi | 409 Conflict |
23514 |
CHECK buzildi | 400 / 422 |
23503 |
foreign key buzildi | 409 yoki 422 |
22P02 |
noto'g'ri formatdagi qiymat | 400 |
constraint maydoni aniqroq javob uchun: vazifalar_egasi_id_matn_key → "Sizda bunday vazifa bor". Cheklov nomlarini bir andozada bergan edik (Amaliyot: real loyiha sxemasi) — mana foydasi.
Oldindan bitta eslatma: takror matn tekshiruvi keyinroq o'zgaradi. Deadlock va serialization failure darsida bu cheklov EXCLUDE … DEFERRABLE ga almashadi (sql/014), shunda xato kodi 23P01 bo'ladi, cheklov nomi — vazifalar_egasi_id_matn_excl. API ikkalasini ham 409 ga aylantiradi. Hozircha, holat 44 da — 23505.
Maslahat: Validatsiyani faqat bazaga qoldirmang: 25-qismda Zod bilan kirishda tekshirasiz. Baza — oxirgi himoya chizig'i: Zod o'tkazib yuborgan yoki ikki parallel so'rov tufayli paydo bo'lgan xatoni (masalan takror matn) faqat baza ushlaydi.
7. SSL va xavfsiz ulanish
Lokal konteyner SSL'siz ishlaydi. Bulut Postgres'i (Neon, Supabase, RDS) odatda SSL (TLS) talab qiladi — trafik shifrlangan bo'lishi uchun. pg da ikki yo'l: manzilda ?sslmode=require yoki obyektda ssl: { … }.
Kanon sslmode=require ni lokal serverga sinadi. Ikki narsa chiqdi:
(node:22544) Warning: SECURITY WARNING: The SSL modes 'prefer', 'require', and 'verify-ca' are treated as aliases for 'verify-full'.
ssl: The server does not support SSL connectionsIkkinchi qator kutilgan: server SSL'ni qo'llamaydi. Birinchisi — pg 8.23 ning ogohlantirishi (to'liq matni bir necha qator). Unga ko'ra hozir require aslida verify-full (sertifikat va host nomini to'liq tekshirish) deb talqin qilinadi; pg 9 da standart libpq ma'nosiga o'tadi (require — faqat shifrlash, sertifikat tekshirilmaydi). Hozirgi xulqni saqlash uchun ogohlantirish sslmode=verify-full ni aniq yozishni maslahat beradi. Bulut provayderining hujjatida qaysi rejim tavsiya qilingan bo'lsa — shuni aniq yozing.
ssl: { rejectUnauthorized: false } ni internetdagi maslahatlarda ko'p uchratasiz — u sertifikat tekshiruvini o'chiradi, ya'ni o'rtadagi odam hujumiga (net, dns va tls — MITM) eshik ochadi. Faqat lokal tajribada.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
r.rows[0].id === 1 har doim false |
bigint → string '1' |
setTypeParser(20) yoki Number(id) |
count + 1 → "481" |
count(*) string |
count(*)::int |
| Sana bir kun oldin chiqadi | date mahalliy zonada Date |
setTypeParser(1082, s => s) |
| Skript tugamaydi | client.end() / pool.end() yo'q |
oxirida yoping |
Har so'rovda yangi Pool |
hovuz qayta yaratilyapti | bitta hovuz, modul darajasida |
ECONNREFUSED |
konteyner to'xtagan, port xato | docker ps, DATABASE_URL |
9. Mashqlar
Mashqlar kurs/mashqlar/26/41-pg-asoslari/ da. Bazaga ulanadigan yechimlar uchun kanon konteyneri (holat 40) ishlab turishi kerak.
1-mashq (oson): bo'shliqlarni to'ldiring
pool.querynatijasidagi qatorlar massivi —r.- UPDATE nechta qatorni o'zgartirganini bildiradi —
r. bigintning OID raqami —count(*)ni son qilib olish uchun SQL'da —count(*)::- Unique cheklov buzilganda SQLSTATE —
Yechim
- rows — har qator obyekt (yoki
rowMode: "array"da massiv). - rowCount —
rowsbo'sh bo'lsa ham (UPDATE, DELETE) soni bor. - 20 —
pg.types.setTypeParser(20, …). - int —
integerturinumberbo'lib keladi (2³¹ gacha — sanash uchun yetarli). - 23505 —
unique_violation; API'da odatda 409.
2-mashq (o'rta): xavfsiz bigint o'giruvchisi
bigintniOgir(s) funksiyasini yozing: satrni oladi, xavfsiz butun son bo'lsa number qaytaradi, bo'lmasa RangeError tashlaydi. Bazasiz — oddiy JS. "48", "9007199254740991" va "9007199254740993" bilan sinang (Number.isSafeInteger — Matn ↔ son va sonni tekshirish).
Yechim
function bigintniOgir(s) {
const n = Number(s);
if (!Number.isSafeInteger(n)) {
throw new RangeError(`bigint ${s} Number'ga sig'maydi`);
}
return n;
}
for (const s of ["48", "9007199254740991", "9007199254740993"]) {
try {
console.log(s, "→", bigintniOgir(s));
} catch (e) {
console.log(s, "→", e.name, e.message);
}
}Konsolda:
48 → 48
9007199254740991 → 9007199254740991
9007199254740993 → RangeError bigint 9007199254740993 Number'ga sig'maydiNumber("9007199254740993") 9007199254740992 beradi — bu butun son, lekin xavfsiz emas (isSafeInteger false), shuning uchun tekshiruv ushlaydi. Number.isInteger ishlatganda ushlamasdi. Shu funksiyani pg.types.setTypeParser(20, bigintniOgir) ga bersangiz — kanon o'giruvchisi.
3-mashq (qiyin): foydalanuvchi statistikasi
Skript yozing (pg, Pool): har foydalanuvchi uchun ismi, jami vazifalari soni va bajarilganlari soni (o'chirilmaganlar — faol_vazifalar view'i), faqat vazifasi borlar, ism bo'yicha tartiblangan. Natijada sonlar number bo'lsin — setTypeParser siz. Har qatorni Ism: bajarildi/jami ko'rinishida chiqaring. (JOIN va GROUP BY — JOIN'lar, Agregatlar.)
Yechim
import pg from "pg";
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
});
const { rows } = await pool.query(`
SELECT f.ism,
count(*)::int AS jami,
count(*) FILTER (WHERE v.bajarildi)::int AS bajarilgan
FROM foydalanuvchilar f
JOIN faol_vazifalar v ON v.egasi_id = f.id
GROUP BY f.id, f.ism
ORDER BY f.ism`);
for (const q of rows) {
console.log(`${q.ism}: ${q.bajarilgan}/${q.jami}`);
}
console.log("tur:", typeof rows[0].jami);
await pool.end();Biz ishga tushirdik (holat 40 namuna ma'lumoti, PostgreSQL 17.6, Node 24.21.0):
Aziz Karimov: 5/12
Bekzod Nazarov: 2/4
Dilnoza Rahimova: 3/6
Gulnora Saidova: 2/4
Javohir Olimov: 3/6
Madina Yusupova: 4/9
Sardor Tursunov: 3/6
tur: numbercount(*)::int — integer turi, pg uni number qiladi. FILTER (WHERE …) — faqat shartga mos qatorlarni sanaydi. Azizda 12 ta: 13-vazifasi Savatda (yumshoq o'chirilgan), view uni ko'rmaydi. Shahnoza (8-foydalanuvchi) yo'q: uning vazifasi yo'q, oddiy JOIN uni tashlab ketdi. Hammani chiqarish kerak bo'lsa — LEFT JOIN va count(v.id).
10. Node qadami: vazifalar-api Postgres'ga ulanadi
Avval tajriba: pg-tajriba papkasida «Turlar» bo'limidagi so'rovni ishga tushiring. date qatori sizda ham bir kun oldin chiqyaptimi? TZ=UTC bilan-chi? Keyin setTypeParser(20) va setTypeParser(1082) ni qo'shib, natija o'zgarganini ko'ring.
Endi loyiha. 25-qismdagi vazifalar-api vazifalarni xotirada yoki JSON faylda saqlardi. Ombor interfeysi (VazifaOmbori) bor edi: server faqat shu interfeysni biladi (qatlamli loyiha tuzilmasi). Shu darsdan boshlab unga uchinchi amalga oshirish qo'shiladi — PgOmbori. Diagrammaga qarang: marshrut va xizmat kodi o'zgarmaydi, faqat ombor tanlovi (SAQLASH) kengayadi.
flowchart LR
M[marshrut va xizmat] --> I{{VazifaOmbori}}
I -->|SAQLASH=xotira| X[XotiraOmbori]
I -->|SAQLASH=fayl| F[FaylOmbori]
I -->|SAQLASH=postgres| P[PgOmbori]
P --> H[pg.Pool]
H --> DB[(Postgres)]Bu ish to'rt darsga bo'lingan:
| Dars | vazifalar-api da nima qo'shiladi |
|---|---|
| shu dars | 013 sxema fayli, pg, PgOmbori ning o'qish metodlari |
| Parametrli so'rovlar | filtrlar, saralash oq ro'yxati, qidiruv |
| Connection pooling | .env sozlamalari, hovuz, ishga tushish va yopilish |
| PgBouncer | yakuniy tekshiruv va commit |
10.1 Sxema: 013-api-tartib.sql
API'ning uchta talabi sxemada yo'q edi (Amaliyot: real loyiha sxemasi darsida "kerak bo'lsa yangi fayl bilan" degan edik):
tartib— foydalanuvchi vazifalarni sudrab tartiblaydi (20-qism), API esa ro'yxatni shu tartibda qaytaradi.- Vaqtlar millisekundgacha. Postgres vaqtni mikrosekundgacha saqlaydi, JavaScript
Dateesa millisekundgacha. Farq qolsa, sahifalash kursori (.123Z) bazadagi.123456dan kichik chiqadi va qator ikkinchi sahifada qayta keladi (Keyset pagination). - Ro'yxat versiyasi —
foydalanuvchilar.vazifalar_versiyasi: har yozuvda +1, API ro'yxatning ETag'i uchun ishlatadi.
Kanon faylidan asosiy qatorlar (to'liq faylda trigger va izohlar ham bor):
ALTER TABLE vazifalar ADD COLUMN tartib integer;
-- mavjudlarga: egasi bo'yicha yaratilish tartibida 1, 2, 3 …
UPDATE vazifalar v SET tartib = t.n
FROM (SELECT id, row_number() OVER (
PARTITION BY egasi_id ORDER BY yaratilgan_vaqt, id) AS n
FROM vazifalar) t
WHERE t.id = v.id;
ALTER TABLE vazifalar ALTER COLUMN tartib SET NOT NULL;
CREATE INDEX vazifalar_tartib_idx ON vazifalar (egasi_id, tartib, id)
WHERE ochirilgan_vaqt IS NULL;
ALTER TABLE vazifalar ALTER COLUMN yaratilgan_vaqt
SET DEFAULT date_trunc('milliseconds', now());
ALTER TABLE foydalanuvchilar
ADD COLUMN vazifalar_versiyasi bigint NOT NULL DEFAULT 0;row_number() OVER (PARTITION BY …) — window funksiya: har egasining vazifalarini alohida raqamlaydi. Faylda yana tartibni_qoy trigger'i bor: tartib berilmasa, yangi vazifa egasining ro'yxati oxiriga tushadi. faol_vazifalar ko'rinishi ham qayta yaratiladi — SELECT * ko'rinish yangi ustunni ko'rmaydi (Amaliyot: real loyiha sxemasi dagi topilma).
Qo'llash — avvalgi fayllar kabi:
psql -X -1 -v ON_ERROR_STOP=1 \
-d postgres://postgres@127.0.0.1:5499/vazifalar \
-f sql/013-api-tartib.sqlTekshiruv. Nimaga qarang: tartib ustunida 3 yo'q — 8-vazifa Savatda, ko'rinish uni ko'rsatmaydi.
SELECT id, tartib, matn
FROM faol_vazifalar
WHERE egasi_id = 1
ORDER BY tartib, id
LIMIT 4;
SELECT id, vazifalar_versiyasi FROM foydalanuvchilar
WHERE id IN (1, 2) ORDER BY id; id | tartib | matn
----+--------+--------------------------------------------------
1 | 1 | JavaScript kursining 26-qismini boshlash #o'qish
2 | 2 | Postgres'ni Docker'da ko'tarish #o'qish #docker
9 | 4 | Onamga dori olib borish #oila
17 | 5 | Portfolio saytiga blog yozish #portfolio
(4 rows)
id | vazifalar_versiyasi
----+---------------------
1 | 13
2 | 6
(2 rows)Biz holat 44 ni o'z PostgreSQL 17.6 klasterimizda noldan qurib ishga tushirdik. Nima bo'ldi? Har vazifa egasi ichida raqam oldi; Azizning ro'yxat versiyasi boshlanishiga 13 — uning vazifalari soni (fayl shunday to'ldiradi).
10.2 pg va PgOmbori ning o'qish qismi
vazifalar-api papkasida:
npm i pg@8.23.1
npm i -D @types/pg@8.23.1@types/pg — TypeScript turlari (pg o'zi JavaScript'da yozilgan). Yangi fayl src/vazifalar/pg-ombor.ts. Uning boshi — bigint o'giruvchisi va hovuz (kanon kodi, uzun qatorlar bo'lingan):
import pg from "pg";
import type { Pool } from "pg";
/** bigint (oid 20) → number, faqat xavfsiz butun son bo'lsa */
function bigintSon(s: string): number {
const n = Number(s);
if (!Number.isSafeInteger(n)) {
throw new RangeError(`bigint ${s} Number'ga sig'maydi`);
}
return n;
}
/** Pool'ning o'z tur o'giruvchilari — global pg.types o'zgarmaydi */
export const TURLAR = new pg.TypeOverrides();
TURLAR.setTypeParser(pg.types.builtins.INT8, bigintSon);
export function pgPool(
url: string,
sozlama: pg.PoolConfig = {},
): Pool {
return new pg.Pool({
connectionString: url,
application_name: "vazifalar-api",
types: TURLAR,
...sozlama,
});
}Uch narsaga qarang:
pg.TypeOverrides— «setTypeParser» bo'limidagi global sozlamaning xavfsiz shakli. O'giruvchi faqat shu hovuzga tegishli (types: TURLAR), boshqa kutubxonalar globalpg.typesni o'zgarmagan holda ko'radi.pg.types.builtins.INT8— o'sha OID 20.bigintSon— 2-mashqdagi funksiya. Endi API javobidaid— son.application_name— server tomonida har ulanish shu nom bilan ko'rinadi (pg_stat_activity). Bazada "bu ulanish kimniki?" degan savolga javob.
O'qish metodi ol(id). Nimaga qarang: egasi sharti va "Savatda emas" sharti — har so'rovda.
async ol(id: number): Promise<Vazifa | undefined> {
const [q] = await this.#bitta<VazifaQatori>(
`SELECT ${QATOR} FROM vazifalar v
WHERE v.id = $1 AND v.egasi_id = $2
AND v.ochirilgan_vaqt IS NULL`,
[id, this.#egasi],
);
return q && vazifa(q);
}QATOR — ustunlar ro'yxati: v.id, v.matn, v.bajarildi, v.tartib va hokazo. Yordamchi #bitta — pool.query ning o'rami: u xatoni API xatosiga aylantiradi («Xato obyekti» bo'limi). Funksiya vazifa(q) qatorni API shakliga o'giradi. Masalan, yaratilgan_vaqt (Date) → yaratildi (ISO satr). Teglar esa matndan olinadi, 25-qismdagi qoida bilan. egasiId konstruktorda beriladi: auth hali yo'q (28-qism), API bitta foydalanuvchining vazifalari bilan ishlaydi. royxat va soni ham shu faylda — ularning shartlari keyingi darsda.
Sinov skripti bilan ko'rdik (holat 44, PostgreSQL 17.6, Node 24.21.0):
ol(9): number 9 [ 'oila' ] 2026-10-03T02:55:00.000Zid — number (string emas), teglar matndan ajratildi, vaqt — ISO satr.
10.3 Shartnoma testlari
25-qismda xotira va fayl omborlari bitta shartnoma testlar to'plamidan (contract testlar) o'tgan edi (omborShartnomasi). Endi Postgres ombori ham xuddi shu testlardan o'tadi — server uchun uchala ombor bir xil ishlashining isboti. Yangi test/pg-ombor.test.ts:
const URL = process.env["TEST_DATABASE_URL"]
?? "postgres://postgres@127.0.0.1:5499/vazifalar_test";
omborShartnomasi("postgres", async () => {
await tozala(pool); // TRUNCATE … RESTART IDENTITY + 2 foydalanuvchi
return new PgOmbori(pool, 1);
});Testlar alohida bazada ishlaydi — vazifalar_test, namuna ma'lumotsiz: har test jadvallarni tozalaydi, haqiqiy bazangizga tegmaydi. Uni bir marta yarating (namuna fayllarini o'tkazib):
createdb -h 127.0.0.1 -p 5499 -U postgres vazifalar_test
for f in sql/0*.sql; do
case $f in *namuna*) continue;; esac
psql -X -q -1 -v ON_ERROR_STOP=1 \
-d postgres://postgres@127.0.0.1:5499/vazifalar_test -f "$f"
doneBaza topilmasa, Postgres testlari yiqilmaydi — o'tkaziladi (SKIP) va sababini yozadi: npm test Docker'siz kompyuterda ham ishlaydi. Kanon chiqishi (yo'q bazaga ulanish, qator qisqartirildi):
﹣ PgOmbori # test bazasi yo'q (…): connect ECONNREFUSEDShartnoma testlaridan tashqari faylda Postgres'ga xos 7 ta test bor (yumshoq o'chirish, trigger teglari, takror matn, injection, kursor…) — ularni keyingi darslarda ko'ramiz. Biz holat 44 testlarini o'z klasterimizda ishga tushirdik (PostgreSQL 17.6, Node 24.21.0):
✔ VazifaOmbori shartnomasi: postgres (658.6044ms)
✔ PgOmbori: Postgres'ga xos (1092.6057ms)
ℹ tests 12
ℹ pass 12
ℹ fail 012 ta Postgres testi o'tdi; kanonda butun to'plam (25-qismdagi testlar bilan) — 79/79. Commit hali yo'q: holat 44 PgBouncer darsida yakunlanadi.
11. Real ishda
- Har Node backend Postgres bilan shu yo'l bilan gaplashadi: to'g'ridan
pg, yoki uning ustidagi vosita — Prisma, Drizzle, Kysely (27-qismda; ular ham ichida drayver ishlatadi). - Tur o'giruvchilari — production'dagi ko'p "sirli" xatolarning manbai: ID taqqoslash, sana surilishi, pul summasi satr bo'lib ulanishi.
- Xato kodlari — yaxshi API "Internal Server Error" emas, "bunday vazifa bor (409)" deydi. SQLSTATE ro'yxati — Postgres hujjatida, Appendix A.
- Intervyuda: "
PoolvaClientfarqi?", "Negabigintstring bo'lib keladi?", "Unique buzilishini qanday aniqlaysiz?"
Xulosa
- Node Postgres bilan drayver orqali gaplashadi;
pg— eng keng tarqalgani (kanon: 8.23.1). Pool— serverda bitta,pool.queryulanishni o'zi oladi va qaytaradi;Client— bitta ulanish, qo'ldaconnect/end.- Natija:
rows,rowCount,command,fields; UPDATE'darowCount: 0→ 404. bigint,count(*),numeric— string;datevatimestamp— mahalliy zonadagiDate(bir kun surilishi mumkin).setTypeParser(20)— xavfsiznumber,setTypeParser(1082, s => s)— sana satr bo'lib qoladi.- Xatoni matn bilan emas,
codevaconstraintbilan aniqlang.
Keyingi dars: Parametrli so'rovlar va SQL injection — nega $1 hayot-mamot masalasi: kanon matnni yopishtirib, boshqalarning 48 ta vazifasini va hamma emaillarni o'g'irladi.
Manbalar
- node-postgres hujjati — Client, Pool, Queries (
rowMode, nomlangan so'rovlar), Data Types: https://node-postgres.com/apis/client, https://node-postgres.com/features/types - pg-types (OID va o'giruvchilar): https://github.com/brianc/node-pg-types
- PostgreSQL 18 hujjati — Appendix A. PostgreSQL Error Codes: https://www.postgresql.org/docs/18/errcodes-appendix.html
- PostgreSQL 18 hujjati — libpq SSL (sslmode qiymatlari): https://www.postgresql.org/docs/18/libpq-ssl.html
- Kanon:
tajriba/t41-pg-asoslari.mjs,natijalar/tajriba/t41-pg-asoslari.txt(PostgreSQL 18.6, Node 24.21.0, pg 8.23.1, 2026-10-24). Ulanish xatolari,dateo'giruvchisi va 3-mashq — biz ishga tushirdik (PostgreSQL 17.6, Node 24.21.0, Windows 11).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!