Files
kolaytercih/scripts/db-index.ts
bilalgursen a773ac7beb feat(veri): 2026 yükleme hattı — yıl kilitli refresh, geçmiş doldurma, arşiv, kalite kapısı (P-A)
- yokatlas-goc: idempotent şema göçü (yıl kolonları, son_kilavuz_yili, eski_kod,
  programs_arsiv, veri_meta) + betiklerin ortak yardımcıları
- refresh: --yil/--db zorunlu, YIL KİLİDİ (…1 = DB sira<yil-1> ≥%95 ve eksiz ≠),
  yıl asla r.yil'den türetilmez; ham veri data/ham/*.jsonl.gz, --ham ile API'siz
  tekrar; benzersiz kod = totalElements; tek transaction + önceki yıl sağlaması;
  yeni programda …1/2/3 → üç önceki yıl; efektif indeks EFEKTIF_SIRA_SQL ile
- gecmis-doldur: yalnız NULL alan; API …1/2/3 → eski_kod → kanıtlı 1:1 ad eşleşmesi
- eski-arsivle: güncel kılavuzda olmayanlar → programs_arsiv, netler yeni koda; --kuru
- detay: netler bağımsız + yıl sağlaması; kırılım yalnız --kirilim, alan yoksa durur,
  mevcut değerin üstüne NULL yazılmaz; `yil - 1` varsayımı kalktı
- veri-kalite: salt okunur kalite kapısı, zorunlu kontrol kalırsa çıkış kodu 1
- docs/veri/yillik-guncelleme.md: adım adım runbook + geri alma

DB commit'lenmedi (arşivleme kararı [Bilal]).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 17:03:01 +03:00

84 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Sorgu yolundaki ifade indexi + WAL bakımı. refresh sonrası (ve istenince tek
// başına) çalıştırılır; tekrar çalıştırmak güvenlidir.
//
// src/lib/db.ts tüm aralık sorgularını EFEKTIF_SIRA_SQL üzerinden yapar;
// SQLite ifade indexleri METİNSEL eşleştiği için indeks ifadesi
// src/lib/veri-yillari.ts'teki sabitle birebir aynı olmalıdır — bu yüzden
// buraya elle yazılmaz, import edilir. Bu index olmadan her sorgu
// tur-partisyonunu tarayıp temp B-tree ile sıralıyordu.
//
// Kullanım: pnpm db:index --db data/yokatlas.db [--vacuum]
// --vacuum: arşivleme gibi büyük silmelerden sonra dosyayı küçültür (site kapalıyken).
import { EFEKTIF_SIRA_SQL } from "../src/lib/veri-yillari";
import { bayrakVar, calistir, dbAc, dbArgumani, dur, type Db } from "./yokatlas-goc";
export const EFEKTIF_INDEKS = "idx_programs_tur_onlisans_efektif";
/**
* Efektif sıra indeksini güncel ifadeyle kurar. Eski yılın ifadesiyle kurulmuş
* aynı adlı indeks varsa DROP edip yeniden kurar (yıl değişince ifade değişir).
*/
export function efektifIndeksiKur(db: Db): "kuruldu" | "yenilendi" | "guncel" {
const mevcut = db
.prepare("SELECT sql FROM sqlite_master WHERE type = 'index' AND name = ?")
.get(EFEKTIF_INDEKS) as { sql: string } | undefined;
let sonuc: "kuruldu" | "yenilendi" | "guncel" = "kuruldu";
if (mevcut) {
if (mevcut.sql.includes(EFEKTIF_SIRA_SQL)) return "guncel";
db.exec(`DROP INDEX ${EFEKTIF_INDEKS}`);
sonuc = "yenilendi";
}
db.exec(
`CREATE INDEX ${EFEKTIF_INDEKS} ON programs (tur, onlisans, ${EFEKTIF_SIRA_SQL})`
);
return sonuc;
}
/** Sihirbaz/liste aralık sorgusunun planı (src/lib/db.ts dilimAra ile aynı biçim). */
export function efektifSorguPlani(db: Db): string[] {
const plan = db
.prepare(
`EXPLAIN QUERY PLAN
SELECT id FROM programs
WHERE tur = ? AND onlisans = ?
AND ${EFEKTIF_SIRA_SQL} IS NOT NULL
AND ${EFEKTIF_SIRA_SQL} BETWEEN ? AND ?
ORDER BY ${EFEKTIF_SIRA_SQL} ASC LIMIT 10`
)
.all("SAYISAL", 0, 1000, 50000) as { detail: string }[];
return plan.map((p) => p.detail);
}
if (process.argv[1]?.endsWith("db-index.ts")) {
calistir(() => {
const db = dbAc(dbArgumani());
console.log(`Efektif indeks (${EFEKTIF_SIRA_SQL}): ${efektifIndeksiKur(db)}`);
// Programatik SEO sayfaları (src/lib/katalog.ts) bölüm/üniversite
// detaylarını ham ada birebir IN (...) eşleşmesiyle çeker; bu iki index
// olmadan her sayfa tam tablo taraması yapar.
db.exec(`
CREATE INDEX IF NOT EXISTS idx_programs_isim ON programs (isim);
CREATE INDEX IF NOT EXISTS idx_programs_universite ON programs (universite);
`);
if (bayrakVar("vacuum")) {
db.exec("VACUUM");
console.log("VACUUM tamam.");
}
// Uygulama DB'yi readonly açtığı için checkpoint yapamaz; şişen WAL'i
// (okuma başına ekstra maliyet) burada dosyaya geri yaz ve sıfırla.
db.pragma("wal_checkpoint(TRUNCATE)");
const plan = efektifSorguPlani(db);
console.log("Sorgu planı:");
for (const satir of plan) console.log(` ${satir}`);
db.close();
if (!plan.some((s) => s.includes(EFEKTIF_INDEKS))) {
dur(`Sorgu planı ${EFEKTIF_INDEKS} indeksini kullanmıyor.`);
}
});
}