---
title: 'Sorgu Nesneleri ve Belirtimler'
source: 'https://academia.sh/tr/kurslar/veri-erisim-katmani/sorgu-nesneleri-ve-belirtimler'
course: 'Veri Erişim Katmanı ve İş Mantığı'
language: tr
updated: '2026-08-17T18:06:50+00:00'
license: 'CC BY-SA 4.0'
---

# Sorgu Nesneleri ve Belirtimler

Ölçütün nesneye dönüşmesi: her belirtimin kendi SQL parçasını ve bağlı değerlerini üretmesi, ve–veya–değil ile birleştirilmesi, aynı ölçütün bellekte yüklem olarak çalışması ve tanıtıcıların izin listesiyle sınırlanması.

Depo kalıbı sınırı kapattı, ama arayüzü şişirdi. Her yeni soru yeni bir yöntem istedi:
açık kayıtlar, üyenin açık kayıtları, tarih aralığındaki kayıtlar, üyenin belirli tarih
aralığındaki açık kayıtları. Ölçütler birleşebildiği için yöntem sayısı ölçüt sayısının
kombinasyonuyla artıyor.

Sorun ölçütlerde değil, ölçütlerin yöntem adına gömülmesindedir. `uyeninAcikKayitlari`
adının içinde iki ölçüt ve bir birleştirici saklıdır; hiçbiri ayrı ayrı kullanılamaz. Bu
ders ölçütü adından çıkarıp nesne yapar. Nesne olan ölçüt birleştirilebilir; birleştirilen
ölçütten tek bir sorgu üretilir.

## Ölçütün Nesneleşmesi

Bir **belirtim** (specification) üç şey taşır: SQL parçası, o parçadaki soru
işaretlerine karşılık gelen değerler ve aynı ölçütün bellek içi karşılığı olan bir yüklem.

```js
// belirtim.mjs — her belirtim hem SQL parcasi hem bellek ici yuklem tasir
const belirtim = (sql, degerler, yuklem) => ({ sql, degerler, yuklem });

export const acik = () =>
  belirtim("iade_tarihi IS NULL", [], (k) => k.iadeTarihi === null);

export const uyenin = (uyeId) =>
  belirtim("uye_id = ?", [uyeId], (k) => k.uyeId === uyeId);

export const kitabin = (kitapId) =>
  belirtim("kitap_id = ?", [kitapId], (k) => k.kitapId === kitapId);

export const alisAraliginda = (bas, son) =>
  belirtim("alis_tarihi BETWEEN ? AND ?", [bas, son],
           (k) => k.alisTarihi >= bas && k.alisTarihi <= son);

export const ve = (...b) =>
  belirtim(`(${b.map((x) => x.sql).join(" AND ")})`,
           b.flatMap((x) => x.degerler),
           (k) => b.every((x) => x.yuklem(k)));

export const veya = (...b) =>
  belirtim(`(${b.map((x) => x.sql).join(" OR ")})`,
           b.flatMap((x) => x.degerler),
           (k) => b.some((x) => x.yuklem(k)));

export const degil = (b) =>
  belirtim(`NOT ${b.sql}`, b.degerler, (k) => !b.yuklem(k));
```

Birleştiricilerin yaptığı iş üç adımdadır: alt belirtimlerin SQL parçalarını parantez
içinde birleştirir, değer dizilerini **aynı sırada** uç uca ekler, yüklemleri mantıksal
olarak bağlar. İkinci adım kritiktir. Soru işaretleri metinde hangi sırayla geçiyorsa,
değerler dizisinde de o sırada bulunmalıdır; `flatMap` bu sırayı, `map` çağrısındaki sırayla
aynı tuttuğu için korur.

## Üretilen Sorgunun Görünür Olması

Belirtimlerin doğruluğu ürettikleri metne bakılarak denetlenir.

```js
// belirtim-yazdir.mjs — bilesik belirtimlerin urettigi SQL ve bagli degerler
import { acik, uyenin, kitabin, alisAraliginda, ve, veya, degil } from "./belirtim.mjs";

const ornekler = {
  "acik": acik(),
  "uyenin acik kayitlari": ve(uyenin(4), acik()),
  "acik degil, 2025 ilk yarisi": ve(degil(acik()), alisAraliginda("2025-01-01", "2025-06-30")),
  "uye 1 veya uye 2, kitap 1": ve(veya(uyenin(1), uyenin(2)), kitabin(1)),
};

for (const [ad, b] of Object.entries(ornekler)) {
  console.log(ad);
  console.log("  sql   :", b.sql);
  console.log("  degerler:", JSON.stringify(b.degerler));
}
```

```sh
node belirtim-yazdir.mjs
```

```
acik
  sql   : iade_tarihi IS NULL
  degerler: []
uyenin acik kayitlari
  sql   : (uye_id = ? AND iade_tarihi IS NULL)
  degerler: [4]
acik degil, 2025 ilk yarisi
  sql   : (NOT iade_tarihi IS NULL AND alis_tarihi BETWEEN ? AND ?)
  degerler: ["2025-01-01","2025-06-30"]
uye 1 veya uye 2, kitap 1
  sql   : ((uye_id = ? OR uye_id = ?) AND kitap_id = ?)
  degerler: [1,2,1]
```

Son örnek sıranın nasıl korunduğunu gösteriyor: üç soru işareti, üç değer, ilk ikisi iç
`veya` belirtiminden, üçüncüsü dıştaki `kitabin` belirtiminden geliyor. Üçüncü örnekte
`degil` birleştiricisi hiç değer taşımıyor, çünkü sardığı belirtim de taşımıyordu; değer
dizisi bu yüzden yalnız iki tarih içeriyor.

Parantezleme ihmal edilirse hata sessiz kalır. `veya` çıktısı parantezsiz olsaydı,
`(A OR B) AND C` yerine `A OR (B AND C)` okunurdu; SQL yine geçerli, sonuç yine dolu, ama
yanlış olurdu.

## Tek Yöntemli Depo

Ölçüt nesneleşince deponun arayüzü küçülür. Süzme dışındaki isteklerle birlikte —
sıralama, sınır, atlama — ölçüt tek bir kayıtta toplanır. Bu kayda **sorgu nesnesi** (query
object) denir.

```js
// sorgu-deposu.mjs — tek arama yontemi; olcut disaridan gelir
const NESNE = (s) => ({ oduncId: s.odunc_id, kitapId: s.kitap_id, uyeId: s.uye_id,
                        alisTarihi: s.alis_tarihi, iadeTarihi: s.iade_tarihi });

export class SorguDeposu {
  constructor(db) { this.db = db; this.uretilen = []; }

  ara(sorgu) {
    const { belirtim, siralama = "odunc_id", yon = "ASC", sinir = null, atlama = 0 } = sorgu;
    let sql = `SELECT * FROM odunc WHERE ${belirtim.sql} ORDER BY ${siralama} ${yon}`;
    const degerler = [...belirtim.degerler];
    if (sinir !== null) { sql += " LIMIT ? OFFSET ?"; degerler.push(sinir, atlama); }
    this.uretilen.push({ sql, degerler });
    return this.db.prepare(sql).all(...degerler).map(NESNE);
  }
}
```

Dört ayrı istek, tek bir yöntemden geçiyor.

```js
// depo-calistir.mjs — ayni depo yontemi uc farkli olcutu karsilar
import { DatabaseSync } from "node:sqlite";
import { SorguDeposu } from "./sorgu-deposu.mjs";
import { acik, uyenin, kitabin, alisAraliginda, ve, veya, degil } from "./belirtim.mjs";

const depo = new SorguDeposu(new DatabaseSync("kutuphane.db"));

const istekler = [
  ["butun acik kayitlar", { belirtim: acik() }],
  ["uye 4'un acik kayitlari", { belirtim: ve(uyenin(4), acik()) }],
  ["kapali, 2025 ilk yarisi, en yenisi",
   { belirtim: ve(degil(acik()), alisAraliginda("2025-01-01", "2025-06-30")),
     siralama: "alis_tarihi", yon: "DESC", sinir: 2 }],
  ["kitap 1'i alan uye 1 veya 2", { belirtim: ve(veya(uyenin(1), uyenin(2)), kitabin(1)) }],
];

for (const [ad, sorgu] of istekler) {
  const sonuc = depo.ara(sorgu);
  console.log(`${ad}: ${sonuc.map((k) => k.oduncId).join(", ") || "(bos)"}`);
}

console.log("\nuretilen sorgular:");
for (const u of depo.uretilen) console.log(`  ${u.sql}\n    <- ${JSON.stringify(u.degerler)}`);
```

Aşağıdaki komut önce kütüphane şemasının ödünç bağıntısını kurar, sonra betiği çalıştırır.

```sh
rm -f kutuphane.db
sqlite3 kutuphane.db <<'SQL'
CREATE TABLE odunc (odunc_id INTEGER PRIMARY KEY, kitap_id INTEGER NOT NULL,
                    uye_id INTEGER NOT NULL, alis_tarihi TEXT NOT NULL, iade_tarihi TEXT);
INSERT INTO odunc VALUES (1,1,1,'2025-01-10','2025-01-24'),(2,2,1,'2025-02-02','2025-02-20'),
  (3,1,2,'2025-02-11',NULL),(4,3,3,'2025-03-01','2025-03-15'),(5,4,3,'2025-03-18','2025-04-02'),
  (6,1,4,'2025-04-05','2025-04-19'),(7,5,4,'2025-04-21',NULL),(8,2,5,'2025-05-02','2025-05-30'),
  (9,7,1,'2025-05-14','2025-05-28'),(10,3,5,'2025-06-03',NULL),(11,6,2,'2025-06-11','2025-06-25'),
  (12,4,4,'2025-06-20','2025-07-04');
SQL
node depo-calistir.mjs
```

```
butun acik kayitlar: 3, 7, 10
uye 4'un acik kayitlari: 7
kapali, 2025 ilk yarisi, en yenisi: 12, 11
kitap 1'i alan uye 1 veya 2: 1, 3

uretilen sorgular:
  SELECT * FROM odunc WHERE iade_tarihi IS NULL ORDER BY odunc_id ASC
    <- []
  SELECT * FROM odunc WHERE (uye_id = ? AND iade_tarihi IS NULL) ORDER BY odunc_id ASC
    <- [4]
  SELECT * FROM odunc WHERE (NOT iade_tarihi IS NULL AND alis_tarihi BETWEEN ? AND ?) ORDER BY alis_tarihi DESC LIMIT ? OFFSET ?
    <- ["2025-01-01","2025-06-30",2,0]
  SELECT * FROM odunc WHERE ((uye_id = ? OR uye_id = ?) AND kitap_id = ?) ORDER BY odunc_id ASC
    <- [1,2,1]
```

Üçüncü sorgu, sınırın ve atlamanın da bağlı değer olarak eklendiğini gösteriyor: ölçütün
iki tarihinden sonra `2` ve `0` geliyor. `uretilen` dizisi, ilk derste kurulan sayma
kancasının bu katmandaki karşılığıdır; hangi ölçütün hangi metne dönüştüğü çalışma anında
görülebiliyor.

## Aynı Ölçüt, İki Yer

Belirtimin ikinci alanı — yüklem — henüz kullanılmadı. Onun değeri, ölçütün veritabanına
gitmeden de sorulabilmesidir. Kaydedilmemiş bir aday kaydın kurala uyup uymadığı bellekte
sınanır.

```js
// bellekte-dogrula.mjs — ayni belirtim, veritabanina gitmeden aday kayda uygulanir
import { acik, uyenin, alisAraliginda, ve } from "./belirtim.mjs";

const kural = ve(uyenin(4), acik(), alisAraliginda("2025-01-01", "2025-12-31"));

const adaylar = [
  { oduncId: null, uyeId: 4, kitapId: 7, alisTarihi: "2025-07-15", iadeTarihi: null },
  { oduncId: null, uyeId: 5, kitapId: 7, alisTarihi: "2025-07-15", iadeTarihi: null },
  { oduncId: null, uyeId: 4, kitapId: 7, alisTarihi: "2024-12-30", iadeTarihi: null },
];

for (const a of adaylar) {
  console.log(`uye=${a.uyeId} alis=${a.alisTarihi} -> ${kural.yuklem(a)}`);
}
console.log("ayni belirtimin SQL karsiligi:", kural.sql);
```

```sh
node bellekte-dogrula.mjs
```

```
uye=4 alis=2025-07-15 -> true
uye=5 alis=2025-07-15 -> false
uye=4 alis=2024-12-30 -> false
ayni belirtimin SQL karsiligi: (uye_id = ? AND iade_tarihi IS NULL AND alis_tarihi BETWEEN ? AND ?)
```

Üç adayın hiçbiri veritabanında değil; kimliklerinin `null` olması bunu gösteriyor. Kural
yine de uygulandı. Kuralın tek bir yerde tanımlanması, sorgu ile denetimin ayrışmasını
önler: iki tanım yazılırsa, biri değiştiğinde öteki sessizce eskir.

Bu ikiliğin bedeli, iki tanımın uyumlu kalma zorunluluğudur. `alisAraliginda` belirtiminde
SQL tarafı `BETWEEN` kullanıyor, yüklem tarafı iki ayrı karşılaştırma yapıyor; ikisinin de
uç değerleri kapsaması gerekir. Böyle bir çift her yazıldığında, iki tarafın aynı sonucu
verdiği sınanmalıdır.

## Bağlı Değer ve Tanıtıcı

Belirtimlerin değerleri soru işaretiyle taşıması bir seçim değil, zorunluluktur. Aynı
girdinin iki yoldan sorguya girmesi arasındaki fark ölçülebilir.

```js
// baglama.mjs — bagli deger ile metin birlestirmenin ayni girdideki farki
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("kutuphane.db");

const girdi = "2 OR 1=1";

const bagli = db.prepare("SELECT count(*) AS n FROM odunc WHERE uye_id = ?").get(girdi).n;
const birlestirilmis = db.prepare(`SELECT count(*) AS n FROM odunc WHERE uye_id = ${girdi}`).get().n;

console.log("bagli degerle donen satir sayisi      :", bagli);
console.log("metin birlestirmeyle donen satir sayisi:", birlestirilmis);
console.log("tablodaki toplam satir sayisi          :",
  db.prepare("SELECT count(*) AS n FROM odunc").get().n);
```

```sh
node baglama.mjs
```

```
bagli degerle donen satir sayisi      : 0
metin birlestirmeyle donen satir sayisi: 12
tablodaki toplam satir sayisi          : 12
```

Bağlı değer olarak gönderilen girdi, sorgunun yapısına değil bir karşılaştırmanın sağ
tarafına yerleşti; hiçbir satır eşleşmedi. Metin birleştirmede aynı girdi `OR 1=1` koşulu
hâline geldi ve bütün tabloyu döndürdü. İleri SQL kursunda tanıtılan SQL enjeksiyonunun
uygulama katmanındaki görünümü budur.

Bağlama her yerde çalışmaz. Tablo ve sütun adları — yani **tanıtıcılar** — bağlı değer
olamaz; sorgu planı bunlar bilinmeden kurulamaz. `SorguDeposu` sınıfındaki `siralama` ve
`yon` alanları metne doğrudan giriyordu. Dışarıdan gelen bir değer bu alanlara ulaşıyorsa
denetim listeyle yapılır.

```js
// tanitici-denetimi.mjs — siralama sutunu baglanamaz, listeyle sinirlanir
import { DatabaseSync } from "node:sqlite";

const SIRALANABILIR = new Set(["odunc_id", "alis_tarihi", "iade_tarihi"]);
const YONLER = new Set(["ASC", "DESC"]);

function guvenliSirala(db, siralama, yon) {
  if (!SIRALANABILIR.has(siralama)) throw new Error(`siralanamaz sutun: ${siralama}`);
  if (!YONLER.has(yon)) throw new Error(`gecersiz yon: ${yon}`);
  return db.prepare(`SELECT odunc_id FROM odunc ORDER BY ${siralama} ${yon} LIMIT 3`).all()
           .map((r) => r.odunc_id);
}

const db = new DatabaseSync("kutuphane.db");
console.log("alis_tarihi DESC:", guvenliSirala(db, "alis_tarihi", "DESC").join(", "));
for (const kotu of ["uye_id", "odunc_id; DROP TABLE odunc"]) {
  try { guvenliSirala(db, kotu, "ASC"); }
  catch (h) { console.log("reddedildi:", h.message); }
}
console.log("odunc tablosu hala yerinde, satir sayisi:",
  db.prepare("SELECT count(*) AS n FROM odunc").get().n);
```

```sh
node tanitici-denetimi.mjs
```

```
alis_tarihi DESC: 12, 11, 10
reddedildi: siralanamaz sutun: uye_id
reddedildi: siralanamaz sutun: odunc_id; DROP TABLE odunc
odunc tablosu hala yerinde, satir sayisi: 12
```

Beyaz liste yaklaşımı kaçış karakteri aramaktan güvenlidir, çünkü izin verilenleri
sayar; yasaklıları tahmin etmeye çalışmaz. Listede olmayan `uye_id` de reddedildi: bu
sütun geçerli olsa bile listede yoksa geçmez.

## Kalıbın Sınırı

Belirtim, ölçütü taşınabilir kılar ama şemayı gizlemez. `uye_id` sütun adı belirtimin
içinde geçiyor; şemada bu ad değişirse belirtim de değişir. Bu, deponun vaadinin bir
adım geriye çekilmesidir: çağıran taraf artık sorgu yazmıyor, ama alan adlarını biliyor.
Ödünleşim bilinçli kabul edilir; alternatifi, her ölçüt bileşimi için ayrı bir depo
yöntemi yazmaktır.

İkinci sınır kapsamdır. Bu belirtimler tek bir bağıntı üzerinde çalışıyor. Birleştirme
gerektiren ölçütler (şubeye göre süzme gibi) belirtimin yalnız `WHERE` parçasını değil
`FROM` parçasını da etkilemesini gerektirir; bu, kalıbı hızla karmaşıklaştırır. Çok tablolu
raporlar ilk derste konulan ayrımın doğrudan sorgu tarafında kalır.

## Özet

- Ölçütü yöntem adına gömmek yerine nesne yapmak, ölçütlerin ve–veya–değil ile
  birleştirilmesini ve tek bir depo yönteminden geçmesini sağlar.
- Her belirtim kendi SQL parçasını ve bağlı değerlerini üretir; birleştiriciler değer
  sırasını SQL metnindeki soru işareti sırasıyla aynı tutar.
- Sorgu nesnesi ölçütün yanında sıralama, sınır ve atlamayı da taşır; üretilen metin ve
  değerler yazdırılarak doğrulanabilir.
- Aynı belirtimin yüklem alanı, kaydedilmemiş bir adayı veritabanına gitmeden denetler;
  kural tek yerde tanımlı kalır.
- Değerler bağlanır, tanıtıcılar bağlanamaz: sıralama sütunu ve yön izin listesiyle
  sınırlanır.

## Sonraki Adım

Sorgular artık tek bir noktadan üretiliyor, ama hepsi aynı şeye ihtiyaç duyuyor: bir
bağlantı. Şimdiye kadar her betik kendi bağlantısını açtı ve iş bitince süreç sona erdi.
Sürekli çalışan bir servis böyle davranamaz; bağlantı açmak pahalıdır ve veritabanının
kabul edebileceği eşzamanlı bağlantı sayısı sınırlıdır. Sonraki ders bağlantıları önceden
açıp ödünç veren havuzu kurar, havuz boyutunu değiştirerek isteklerin kuyrukta bekleme
süresini paralel çağrılarla ölçer ve doygunluk noktasının nerede belirdiğini gösterir.
