PostgreSQL bilan bog‘lanish (pgx / database/sql)
Bu darsda Go dasturini PostgreSQL bazasiga ulaymiz va kitoblar ustida CRUD amallarini bajaramiz. Avval standart database/sql interfeysi, keyin pgxpool bilan ishlashni ko‘ramiz. Oxirida clean architecture darsidagi repository interfeysini PostgreSQL uchun amalga oshiramiz.
1. PostgreSQL’ni ishga tushirish
PostgreSQL’ni kompyuterga o‘rnatish shart emas. Uni Docker konteynerida ishga tushirish qulayroq:
docker run -d \
--name books-db \
-e POSTGRES_USER=app \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=books \
-p 5432:5432 \
-v books-data:/var/lib/postgresql/data \
postgres:17
Buyruq parametrlari:
--name books-db— konteyner nomi;-e ...— foydalanuvchi, parol va baza nomi;-p 5432:5432— konteynerdagi 5432-portni kompyuterning 5432-portiga ulaydi;-v books-data:...— ma’lumotlarbooks-datanomli Docker volume’da saqlanadi. Konteyner o‘chirilsa ham ular yo‘qolmaydi;postgres:17— PostgreSQL’ning 17-versiyasi. Rasmiy Docker Hub sahifasidan joriy versiyani tekshirib oling.
Baza tayyorligini tekshiramiz:
Jadval yaratish
psql — PostgreSQL’ning buyruq qatori klienti. U konteyner ichida tayyor turibdi:
books=# taklifi chiqqach, jadvalni yaratamiz:
CREATE TABLE books (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
author TEXT NOT NULL,
year INT NOT NULL CHECK (year > 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (title, author)
);
\dt buyrug‘i jadvallar ro‘yxatini, \q esa psqldan chiqishni bildiradi.
Info
Real loyihada jadvallar qo‘lda emas, migratsiyalar orqali yaratiladi. Migratsiya — baza tuzilishidagi har bir o‘zgarishni tartib raqami bilan saqlaydigan SQL fayl. golang-migrate, goose yoki atlas kabi vositalar ularni ketma-ket qo‘llaydi. Shunda baza tuzilishi ham kod kabi Git’da saqlanadi.
2. Ulanish satri (DSN)
Go dasturi bazaga qanday ulanishni DSN (Data Source Name) — ulanish satridan biladi. PostgreSQL uchun u URL ko‘rinishida yoziladi:
postgres://app:secret@localhost:5432/books?sslmode=disable
└──┬───┘ └┬┘ └─┬──┘ └───┬───┘ └┬─┘ └─┬─┘ └──────┬──────┘
sxema user parol host port baza parametrlar
sslmode=disable — lokal ishlab chiqish uchun shifrlashni o‘chiradi. Production’da baza boshqa serverda bo‘lsa, sslmode=verify-full ishlatiladi.
Warning
DSN ichida parol bor. Uni kodga yozmang va Git’ga qo‘shmang. Bu darsda soddalik uchun o‘zgaruvchidan o‘qiymiz. To‘g‘ri usul Config va .env darsida ko‘riladi.
3. database/sql bilan ishlash
Loyihani tayyorlaymiz:
pgx paketining ichida database/sql uchun drayver bor: github.com/jackc/pgx/v5/stdlib.
Ulanishni ochish
import (
"database/sql"
_ "github.com/jackc/pgx/v5/stdlib"
)
db, err := sql.Open("pgx", os.Getenv("DATABASE_URL"))
_bilan import drayverni faqat ro‘yxatdan o‘tkazish uchun kerak. Paketninginitfunksiyasi"pgx"nomli drayvernidatabase/sqlga qo‘shadi.sql.Openulanish ochmaydi. U faqat DSN’ni tekshiradi va*sql.DBobyektini tayyorlaydi. Haqiqiy ulanish birinchi so‘rov paytida ochiladi.- Shuning uchun darhol
db.PingContext(ctx)chaqirib, baza haqiqatan ham javob berayotganini tekshirish kerak.
*sql.DB — bitta ulanish emas, pul
*sql.DB bitta ulanish emas. U ulanishlar puli (connection pool): kerak paytda yangi ulanish ochadi, bo‘shaganlarini qayta ishlatadi. U goroutine’lar uchun xavfsiz, ya’ni bir vaqtda bir nechta goroutine undan foydalana oladi. Shu sababli sql.DB dastur boshida bir marta yaratiladi va butun dastur davomida ishlatiladi.
Pul sozlamalari:
db.SetMaxOpenConns(25) // bir vaqtda ochiq ulanishlar soni
db.SetMaxIdleConns(25) // kutib turgan ulanishlar soni
db.SetConnMaxLifetime(30 * time.Minute) // ulanishning maksimal umri
db.SetConnMaxIdleTime(5 * time.Minute) // bo‘sh turgan ulanish qancha saqlanadi
SetMaxOpenConns chegarasiz qoldirilsa, yuklama oshganda ilova minglab ulanish ochib, bazani to‘ldirib qo‘yishi mumkin. PostgreSQL’da standart ulanishlar chegarasi 100 ta. Bu son barcha server nusxalari uchun umumiy.
Uch turdagi so‘rov
| Metod | Qachon | Nima qaytaradi |
|---|---|---|
ExecContext |
INSERT, UPDATE, DELETE — natija qatorlari kerak emas |
sql.Result (ta’sirlangan qatorlar soni) |
QueryRowContext |
Bitta qator kutilganda | *sql.Row |
QueryContext |
Bir nechta qator kutilganda | *sql.Rows |
Barcha metodlarning Context qo‘shimchali shakli bor. Ularning ahamiyati keyingi darsda tushuntiriladi. Hozircha doim ...Context metodlaridan foydalanamiz.
To‘liq misol
| main.go | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
Bazaga ulandik
Yangi kitob ID: 1
Topildi: O‘tkan kunlar (1926)
999-kitob topilmadi
1930-yildan keyingi kitoblar: 2
2. Kecha va kunduz — Cho‘lpon
3. Sarob — Abdulla Qahhor
Yangilangan qatorlar: 1
O‘chirilgan qatorlar: 0
Muhim qoidalar
Parametrlar $1, $2 orqali beriladi. Qiymatlar hech qachon SQL satriga fmt.Sprintf yoki + bilan qo‘shilmaydi. Bu SQL injection’dan himoyaning asosi. Mavzu alohida darsda batafsil ko‘riladi.
RETURNING id — PostgreSQL’ga xos imkoniyat. INSERT natijasida yaratilgan qatordagi qiymatlarni qaytaradi. Shu sababli bu yerda ExecContext emas, QueryRowContext ishlatildi.
sql.ErrNoRows — QueryRow hech qanday qator topmasa qaytadi. Bu xato emas, oddiy holat: API’da u 404 Not Foundga aylanadi.
rows.Close() majburiy. Qatorlar yopilmasa, ulanish pulga qaytmaydi. Bir necha yuz so‘rovdan keyin pul tugaydi va ilova "osilib" qoladi. defer rows.Close()ni QueryContextdan keyin, xato tekshirilgan zahoti yozing.
rows.Err() tekshiriladi. rows.Next() false qaytarganda sabab ikki xil bo‘lishi mumkin: qatorlar tugadi yoki o‘qish paytida xato yuz berdi. Farqni faqat rows.Err() ko‘rsatadi.
RowsAffected UPDATE yoki DELETE haqiqatan biror qatorga ta’sir qilganini bildiradi. 0 bo‘lsa, yozuv topilmagan. API’da bu ham 404ga aylanadi.
NULL qiymatlar
Ustun NULL bo‘lishi mumkin bo‘lsa, uni oddiy stringga o‘qib bo‘lmaydi. Buning uchun ko‘rsatkich yoki sql.Null* turlari ishlatiladi:
var description *string // NULL → nil
var isbn sql.NullString // NULL → isbn.Valid == false
row.Scan(&description, &isbn)
4. pgxpool bilan ishlash
pgx kutubxonasining o‘z interfeysi PostgreSQL uchun maxsus ishlab chiqilgan. U database/sqlga qaraganda biroz tezroq ishlaydi va PostgreSQL’ning massivlar, JSONB, COPY kabi imkoniyatlarini to‘liq qo‘llab-quvvatlaydi. Loyiha faqat PostgreSQL bilan ishlasa, pgxpool yaxshi tanlov.
database/sqldan farqlari:
- Topilmagan qator uchun
sql.ErrNoRowsemas,pgx.ErrNoRowsqaytadi. pgx.CollectRowsvapgx.RowToStructByNameqatorlarni ustun nomlari bo‘yicha struct’ga o‘zi joylaydi.dbtagi ustun nomini ko‘rsatadi.rows.Close()ham avtomatik chaqiriladi.- Pul sozlamalari
pgxpool.Configorqali beriladi. Ularni DSN ichida ham yozish mumkin:?pool_max_conns=25.
5. PostgreSQL repository
Endi clean architecture darsidagi book.Repository interfeysini PostgreSQL uchun amalga oshiramiz. Interfeys o‘zgarmaydi:
type Repository interface {
List(ctx context.Context) ([]Book, error)
Get(ctx context.Context, id int) (Book, error)
Create(ctx context.Context, b Book) (Book, error)
ExistsByTitle(ctx context.Context, title, author string) (bool, error)
}
Repository’da e’tibor berishga arziydigan jihatlar:
- Baza xatolari domen xatolariga aylantiriladi.
pgx.ErrNoRows→book.ErrNotFound, PostgreSQL’ning23505(unique_violation) kodi →book.ErrDuplicate. Service va handlerpgxhaqida hech narsa bilmaydi. ExistsByTitlebo‘lsa ham,UNIQUEcheklovi kerak. Ikki so‘rov bir vaqtda kelsa, ikkalasi ham "mavjud emas" javobini olib, keyin ikkalasi ham yozishga urinishi mumkin. Bunday holatda baza cheklovi ikkinchisini to‘xtatadi, repository esa xatoniErrDuplicatega aylantiradi.- Xatolar kontekst bilan o‘raladi:
fmt.Errorf("kitob #%d: %w", id, err). Logda xato qayerda yuz bergani ko‘rinadi,%wesa asl xatoni saqlab qoladi.
main.goda faqat repository yaratilishi o‘zgaradi:
pool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
defer pool.Close()
repo := storage.NewPostgresBookRepo(pool) // oldin: storage.NewMemoryBookRepo()
service := book.NewService(repo)
handler := httpapi.NewHandler(service)
Service, handler va ularning testlari o‘zgarmadi. Qatlamlarga ajratishning foydasi aynan shu yerda ko‘rinadi.
Xulosa
- PostgreSQL’ni Docker’da bitta buyruq bilan ishga tushirish mumkin.
sql.Openulanish ochmaydi. UlanishniPingContextbilan tekshiring.*sql.DBva*pgxpool.Pool— ulanishlar puli. Ular dastur boshida bir marta yaratiladi.- Qiymatlar doim
$1,$2parametrlari orqali uzatiladi. rows.Close()varows.Err()majburiy.- Topilmagan qator (
ErrNoRows) va baza cheklovi xatolari repository’da domen xatolariga aylantiriladi.
Keyingi darsda context yordamida sekin so‘rovlarni bekor qilish, timeout qo‘yish va tranzaksiyalar bilan ishlashni o‘rganamiz.