Spaces:
Running on Zero
Running on Zero
File size: 11,726 Bytes
65083ae | 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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | """database.py — Veritabanı katmanı.
Bu modül SQLite ile ilgili HER ŞEYİ içerir; başka hiçbir dosya doğrudan SQL
yazmaz. Böylece veritabanı değişirse (örneğin PostgreSQL'e geçilirse) yalnızca
bu dosya değişir.
Tablolar:
seferler — uçuş bilgileri ve boş koltuk sayısı
rezervasyonlar — yapılan rezervasyonlar (seferler tablosuna bağlı)
"""
import os
import random
import sqlite3
import string
from datetime import datetime, timedelta
VERITABANI = os.environ.get("DB_YOLU", "ucus.db")
# ---------------------------------------------------------------------------
# Bağlantı
# ---------------------------------------------------------------------------
def baglan():
"""Veritabanı bağlantısı açar.
row_factory = sqlite3.Row sayesinde sonuçlara sütun adıyla erişilebilir:
satir["kalkis"] yerine satir[1]
Bu, kodu okunabilir kılar ve sütun sırası değişse bile bozulmaz.
foreign_keys = ON: SQLite'ta yabancı anahtar denetimi varsayılan olarak
KAPALIDIR; her bağlantıda açılması gerekir. Bu olmadan, var olmayan bir
sefere rezervasyon eklenebilirdi.
"""
conn = sqlite3.connect(VERITABANI)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA foreign_keys = ON")
return conn
# ---------------------------------------------------------------------------
# Şema
# ---------------------------------------------------------------------------
def tablolari_olustur():
"""Tabloları oluşturur (yoksa)."""
with baglan() as conn:
conn.executescript("""
CREATE TABLE IF NOT EXISTS seferler (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ucus_kodu TEXT NOT NULL UNIQUE,
firma TEXT NOT NULL,
kalkis TEXT NOT NULL,
varis TEXT NOT NULL,
tarih TEXT NOT NULL,
kalkis_saati TEXT NOT NULL,
varis_saati TEXT NOT NULL,
fiyat REAL NOT NULL,
bos_koltuk INTEGER NOT NULL CHECK (bos_koltuk >= 0)
);
CREATE TABLE IF NOT EXISTS rezervasyonlar (
pnr TEXT PRIMARY KEY,
sefer_id INTEGER NOT NULL,
yolcu_adi TEXT NOT NULL,
koltuk_sayisi INTEGER NOT NULL CHECK (koltuk_sayisi > 0),
toplam_fiyat REAL NOT NULL,
durum TEXT NOT NULL DEFAULT 'onaylandi',
olusturma_tarihi TEXT NOT NULL,
FOREIGN KEY (sefer_id) REFERENCES seferler(id)
);
CREATE INDEX IF NOT EXISTS idx_sefer_arama
ON seferler(kalkis, varis, tarih);
""")
# CHECK (bos_koltuk >= 0):
# Veritabanı düzeyinde koruma. Bir hata sonucu koltuk sayısı eksiye
# düşmeye çalışırsa SQLite işlemi reddeder. Koddaki kontrolün yedeğidir.
#
# UNIQUE (ucus_kodu):
# Aynı uçuş kodu iki kez eklenemez.
#
# INDEX:
# Arama sorgusu (kalkis + varis + tarih) sık çalışacağı için hızlandırılır.
# ---------------------------------------------------------------------------
# Örnek veri
# ---------------------------------------------------------------------------
SEHIRLER = [
("İstanbul", "IST"), ("Ankara", "ESB"), ("İzmir", "ADB"),
("Antalya", "AYT"), ("Trabzon", "TZX"), ("Adana", "ADA"),
("Gaziantep", "GZT"), ("Dalaman", "DLM"),
]
FIRMALAR = ["Anadolu Hava", "Marmara Havayolları", "Ege Air", "Toros Havacılık"]
def ornek_veri_ekle(sefer_sayisi=28):
"""Rastgele ama gerçekçi seferler üretir.
Sabit tohum (seed) kullanılır: her çalıştırmada aynı veri oluşur.
Böylece test sonuçları tekrarlanabilir olur.
"""
with baglan() as conn:
mevcut = conn.execute("SELECT COUNT(*) FROM seferler").fetchone()[0]
if mevcut > 0:
return mevcut # veri zaten var, tekrar ekleme
rnd = random.Random(42)
bugun = datetime.now().date()
eklenen = 0
kullanilan_kodlar = set()
while eklenen < sefer_sayisi:
kalkis, kalkis_kod = rnd.choice(SEHIRLER)
varis, _ = rnd.choice(SEHIRLER)
if kalkis == varis:
continue # aynı şehre uçuş olmaz
# Uçuş kodu: TK1234 gibi
kod = f"{kalkis_kod[:2]}{rnd.randint(1000, 9999)}"
if kod in kullanilan_kodlar:
continue
kullanilan_kodlar.add(kod)
tarih = bugun + timedelta(days=rnd.randint(1, 14))
kalkis_saat = rnd.choice(
["06:30", "08:15", "10:45", "13:20", "15:50", "18:30", "21:10"])
# Varış saati: 1-2 saat sonra
sa, dk = map(int, kalkis_saat.split(":"))
sure = rnd.choice([60, 75, 90, 105, 120])
varis_dt = datetime(2000, 1, 1, sa, dk) + timedelta(minutes=sure)
conn.execute("""
INSERT INTO seferler
(ucus_kodu, firma, kalkis, varis, tarih,
kalkis_saati, varis_saati, fiyat, bos_koltuk)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
kod,
rnd.choice(FIRMALAR),
kalkis, varis,
tarih.isoformat(),
kalkis_saat,
varis_dt.strftime("%H:%M"),
round(rnd.uniform(850, 4200), 2),
rnd.randint(0, 60), # bazı seferler dolu olabilir (0)
))
eklenen += 1
return eklenen
# ---------------------------------------------------------------------------
# Okuma işlemleri
# ---------------------------------------------------------------------------
def sefer_ara(kalkis=None, varis=None, tarih=None, limit=10):
"""Seferleri arar. Parametreler isteğe bağlıdır; verilenler filtre olur."""
sorgu = "SELECT * FROM seferler WHERE bos_koltuk > 0"
parametreler = []
# Sorguyu parça parça kur: yalnızca verilen filtreler eklenir.
# Dikkat: değerler doğrudan metne eklenmez, ? ile parametre olarak geçer.
# Bu, SQL injection saldırılarını önler.
if kalkis:
sorgu += " AND LOWER(kalkis) LIKE LOWER(?)"
parametreler.append(f"%{kalkis}%")
if varis:
sorgu += " AND LOWER(varis) LIKE LOWER(?)"
parametreler.append(f"%{varis}%")
if tarih:
sorgu += " AND tarih = ?"
parametreler.append(tarih)
sorgu += " ORDER BY tarih, kalkis_saati LIMIT ?"
parametreler.append(limit)
with baglan() as conn:
satirlar = conn.execute(sorgu, parametreler).fetchall()
return [dict(s) for s in satirlar]
def sefer_getir(sefer_id):
"""Tek bir seferi id ile getirir. Yoksa None döner."""
with baglan() as conn:
satir = conn.execute(
"SELECT * FROM seferler WHERE id = ?", (sefer_id,)).fetchone()
return dict(satir) if satir else None
def rezervasyon_getir(pnr):
"""PNR ile rezervasyonu, bağlı olduğu sefer bilgisiyle birlikte getirir.
JOIN: iki tabloyu birleştirir. rezervasyonlar.sefer_id ile
seferler.id eşleştirilerek uçuş detayları da tek sorguda alınır.
"""
with baglan() as conn:
satir = conn.execute("""
SELECT r.*, s.ucus_kodu, s.firma, s.kalkis, s.varis,
s.tarih, s.kalkis_saati, s.varis_saati
FROM rezervasyonlar r
JOIN seferler s ON r.sefer_id = s.id
WHERE r.pnr = ?
""", (pnr.upper(),)).fetchone()
return dict(satir) if satir else None
# ---------------------------------------------------------------------------
# Yazma işlemi
# ---------------------------------------------------------------------------
def _pnr_uret():
"""6 karakterlik rezervasyon kodu üretir (örn. 'K7X2M9')."""
karakterler = string.ascii_uppercase + string.digits
return "".join(random.choices(karakterler, k=6))
def rezervasyon_olustur(sefer_id, yolcu_adi, koltuk_sayisi):
"""Rezervasyon oluşturur ve seferin boş koltuk sayısını düşürür.
Bu iki işlem TEK BİR TRANSACTION içinde yapılır: ya ikisi de gerçekleşir
ya da hiçbiri. Aksi hâlde rezervasyon kaydedilip koltuk düşmezse aynı
koltuk iki kez satılabilir.
Dönüş: (basarili: bool, sonuc: dict)
"""
conn = baglan()
try:
# BEGIN IMMEDIATE: yazma kilidini hemen alır. Aynı anda iki kişi
# son koltuğu almaya çalışırsa biri beklemek zorunda kalır.
conn.execute("BEGIN IMMEDIATE")
sefer = conn.execute(
"SELECT * FROM seferler WHERE id = ?", (sefer_id,)).fetchone()
# --- Doğrulama 1: sefer var mı? ---
# Model uydurma bir sefer_id gönderirse burada durur.
if sefer is None:
conn.rollback()
return False, {"hata": f"{sefer_id} numaralı sefer bulunamadı."}
# --- Doğrulama 2: yeterli koltuk var mı? ---
if sefer["bos_koltuk"] < koltuk_sayisi:
conn.rollback()
return False, {
"hata": f"Yetersiz koltuk. Talep: {koltuk_sayisi}, "
f"mevcut: {sefer['bos_koltuk']}."
}
# --- Benzersiz PNR üret ---
for _ in range(10):
pnr = _pnr_uret()
varsa = conn.execute(
"SELECT 1 FROM rezervasyonlar WHERE pnr = ?", (pnr,)).fetchone()
if not varsa:
break
else:
conn.rollback()
return False, {"hata": "Rezervasyon kodu üretilemedi, tekrar deneyin."}
toplam = round(sefer["fiyat"] * koltuk_sayisi, 2)
# --- İşlem 1: rezervasyonu kaydet ---
conn.execute("""
INSERT INTO rezervasyonlar
(pnr, sefer_id, yolcu_adi, koltuk_sayisi,
toplam_fiyat, durum, olusturma_tarihi)
VALUES (?, ?, ?, ?, ?, 'onaylandi', ?)
""", (pnr, sefer_id, yolcu_adi, koltuk_sayisi, toplam,
datetime.now().isoformat(timespec="seconds")))
# --- İşlem 2: koltuk sayısını düşür ---
conn.execute(
"UPDATE seferler SET bos_koltuk = bos_koltuk - ? WHERE id = ?",
(koltuk_sayisi, sefer_id))
conn.commit() # ikisi birden kalıcı olur
return True, {
"pnr": pnr,
"ucus_kodu": sefer["ucus_kodu"],
"firma": sefer["firma"],
"kalkis": sefer["kalkis"],
"varis": sefer["varis"],
"tarih": sefer["tarih"],
"kalkis_saati": sefer["kalkis_saati"],
"yolcu_adi": yolcu_adi,
"koltuk_sayisi": koltuk_sayisi,
"toplam_fiyat": toplam,
"kalan_koltuk": sefer["bos_koltuk"] - koltuk_sayisi,
}
except sqlite3.Error as e:
conn.rollback() # hata olursa hiçbir değişiklik kalmaz
return False, {"hata": f"Veritabanı hatası: {e}"}
finally:
conn.close()
# ---------------------------------------------------------------------------
def hazirla():
"""Uygulama başlarken çağrılır: tabloları kurar, veri yoksa ekler."""
tablolari_olustur()
return ornek_veri_ekle()
if __name__ == "__main__":
sayi = hazirla()
print(f"Veritabanı hazır: {VERITABANI}")
with baglan() as c:
toplam = c.execute("SELECT COUNT(*) FROM seferler").fetchone()[0]
rez = c.execute("SELECT COUNT(*) FROM rezervasyonlar").fetchone()[0]
print(f" Sefer sayısı : {toplam}")
print(f" Rezervasyon sayısı : {rez}")
|