- 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>
84 lines
3.2 KiB
TypeScript
84 lines
3.2 KiB
TypeScript
// 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.`);
|
||
}
|
||
});
|
||
}
|