Binding: Prepared Statement'a Değer Aktarmanın Yolu
Prepared statement aslında içinde boşluklar olan bir SQL'dir. Binding (parametre bağlama), bu boşlukları değerlerle doldurma işlemidir — string birleştirerek değil, sürücünün API'si üzerinden, her birini güvenli bir şekilde tek tek geçirerek.
Mantık her zaman aynı: SQL'i yer tutucularla yaz, değerleri de ayrı olarak ilet.
CLI üzerinde parametre bağlamayı (binding) gerçek anlamda göstermek mümkün değil — çünkü kabuğun arkasına bağlı bir uygulama kodu yok. Ama yukarıdaki SQL, uygulamanızın veritabanına gönderdiği şeyin tam olarak aynısı. Buradaki ? işaretleri birer yer tutucu. Sürücünüz — Python'da sqlite3, Node tarafında better-sqlite3, Rust'ta rusqlite — bu yer tutucuları ayrı bir bind çağrısıyla dolduruyor.
Şöyle düşünün: SQL tarif, bağlanan değerler ise malzemeler. İkisi birbirine asla doğrudan değmez.
Konumsal yer tutucular: ?
En sade yer tutucu ? işaretidir. Her ?, sırayla bağladığınız bir sonraki değerle eşleşir.
INSERT INTO users (name, email) VALUES (?, ?);
Python tarafında ise şöyle yazılır:
cursor.execute(
"INSERT INTO users (name, email) VALUES (?, ?)",
("Rosa", "rosa@example.com"),
)
İlk ? "Rosa" değerini, ikincisi ise "rosa@example.com" değerini alır. Eksik ya da fazla değer gönderirseniz, sürücü ifadeyi çalıştırmadan hata fırlatır.
Yer tutucuları ?1, ?2, ?3 şeklinde açıkça numaralandırabilirsiniz de — özellikle aynı değeri birden fazla yerde kullandığınız durumlarda işe yarar:
SELECT ?1 AS selamlama, ?1 AS hala_ayni;
?1 aynı değeri tekrar tekrar kullanmanı sağlar; numara vermezsen aynı değeri iki ayrı yere bağlamak zorunda kalırdın.
İsimli Yer Tutucular: :name
Bir sorguda iki üçten fazla yer tutucu olmaya başladığı anda, konumsal parametrelerle uğraşmak resmen tahmin oyununa dönüyor. İşte tam burada sqlite isimli parametreler imdada yetişiyor:
INSERT INTO users (name, email)
VALUES (:name, :email);
In Python:
cursor.execute(
"INSERT INTO users (name, email) VALUES (:name, :email)",
{"name": "Boris", "email": "boris@example.com"},
)
Sözlükteki anahtarların sırası önemli değil; sadece isimleri önemli. SQLite ayrıca @name ve $name ön eklerini de kabul eder; üçü de aynı şekilde çalışır. Pratikte en yaygın kullanılanı açık ara :name.
İsimli parametreler asıl değerini, beş sütunlu bir UPDATE yazdığınızda ya da aynı değeri hem WHERE hem de RETURNING içinde kullandığınız sorgularda gösterir.
NULL değer bağlama
Bir sütuna NULL eklemenin doğru yolu, kullandığınız dilin null değerini parametre bağlama API'sine geçirmektir. Çevirisini sürücü kendisi halleder:
INSERT INTO users (name, email) VALUES (?, ?);
-- Bağla: ("Cyrus", None) Python'da
-- Bağla: ["Cyrus", null] Node'da
SELECT id, name, email FROM users;
None, null, nil — diliniz ne diyorsa desin, sürücü bunu gerçek bir SQL NULL değerine çevirir. Sakın "NULL" string'ini bağlamayın; o, dört karakterlik "NULL" metnini saklar. NULL kelimesini SQL metninin içine doğrudan yerleştirmek de olmaz — bu, parametre bağlamanın bütün anlamını ortadan kaldırır.
Aynı kural sayılar, blob'lar ve tarihler için de geçerli: değeri kendi yerel tipinde gönderin, bağlama işini sürücüye bırakın.
Aynı İfadeyi Farklı Değerlerle Tekrar Tekrar Kullanmak
Parametre bağlama, sqlite prepared statement yapısıyla birlikte çok iyi çalışır. Bir kez hazırlayın (prepare), sonra istediğiniz kadar bağlayıp çalıştırın. Parser işini yalnızca bir defa yapar; veritabanı ise derlenmiş planı her bağlanan değer kümesi için yeniden kullanır.
INSERT INTO users (name, email) VALUES (?, ?);
-- ("Ada", "ada@example.com") değerlerini bağla -> çalıştır
-- ("Boris", "boris@example.com") değerlerini bağla -> çalıştır
-- ("Cyrus", NULL) değerlerini bağla -> çalıştır
SELECT id, name, email FROM users ORDER BY id;
Çoğu sürücü bunu Python'da executemany, Node'da ise bir .run() döngüsüyle sarmalar. Hangi yolu seçerseniz seçin, kazandığınız şey ayrıştırma (parsing) maliyetidir — tek başına bakıldığında küçük gibi görünür ama binlerce satır eklerken farkı net biçimde hissedersiniz.
Aynı sorguda farklı stilleri karıştırmayın
SQLite, teknik olarak aynı ifadede hem konumsal hem de isimli yer tutucuları kullanmanıza izin verir. Yine de bundan uzak durun.
-- Yasal ama tehlikeli bir tuzak:
INSERT INTO users (name, email) VALUES (?, :email);
Okuyucu, aynı anda iki farklı bağlama API'sini kafasında tutmak zorunda kalır ve sürücülerin çoğu karışık kullanımı düzgün desteklemez. Her ifade için tek bir stil seçin: bir ya da iki değer için ?, geri kalan her şey için :name.
Sık Yapılan Hata: Parametre Bağlama, String Biçimlendirme Değildir
Parametre bağlamanın bütün amacı, değerlerin SQL ayrıştırıcısından geçmemesidir. Şu iki Python satırını karşılaştırın:
# Yanlış — string biçimlendirme:
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
# Doğru — parametre bağlama:
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))
İlk satır SQL'i string birleştirmeyle kuruyor. name değişkeni "'; DROP TABLE users; --" olursa, veritabanı bu enjekte edilmiş ifadeyi hiç tereddüt etmeden ayrıştırıp çalıştırır. İkinci satır ise SQL'i ve değeri ayrı kanallardan gönderiyor — değer ne içerirse içersiniz, sadece bir string olarak bağlanır, başka bir şey değil. Her rehberin "parametreleri bağla" demesinin sebebi bu: mesele stil değil, parser'ın ne gördüğüyle ilgili.
Enjeksiyon tarafını bir sonraki sayfada daha ayrıntılı ele alacağız.
Bir Başka Tuzak: Tanımlayıcıları Bağlayamazsınız
Yer tutucular yalnızca değerler için işe yarar — string'ler, sayılar, blob'lar, NULL'lar. Tablo adları, sütun adları veya SQL anahtar kelimeleri için çalışmazlar:
-- Bu, istediğiniz şeyi YAPMAZ:
SELECT * FROM ? WHERE id = ?;
-- İlk ?, tablo adı olarak değil, string literali olarak bağlanır.
Eğer gerçekten dinamik bir tablo veya sütun adına ihtiyacınız varsa (uygulama kodunda nadiren olur), bunu bir izin listesine (allow-list) karşı doğrulayın ve SQL'e kendiniz birleştirin — kesinlikle doğrudan kullanıcı girdisinden almayın. Geri kalan her şey için parametre bağlama kullanın.
Uçtan Uca Bir Örnek
Şimdi parçaları bir araya getirelim — küçük bir users tablosunu tamamen parametre bağlama ile yazıp okuyalım:
Gerçek uygulama kodunda hem INSERT'ler hem de SELECT mutlaka yer tutucu kullanır. CLI'da bağlama yapacak bir uygulama olmadığı için, bağlamanın ürettiği değerlerin yerine düz literaller yazıyoruz.
Sırada: SQL Injection'ı Önlemek
Parametre bağlama bir mekanizma sadece. Neden SQL injection'ı engellediğini ve sadece bağlamanın yetmediği o birkaç köşe durumu bir sonraki sayfada ele alacağız.
Sıkça Sorulan Sorular
SQLite'ta parametre bağlama (parameter binding) nedir?
Parametre bağlama, hazırlanmış (prepared) bir ifadeye değerleri SQL metninden ayrı olarak iletmenin yoludur. SQL içinde ? veya :name gibi bir yer tutucu yazarsınız, asıl değeri ise sürücünün bind API'si üzerinden geçirirsiniz. SQLite, bağlanan değerleri tamamen veri olarak görür — hiçbir zaman SQL gibi yorumlamaz.
SQLite'ta ? ile :name arasındaki fark nedir?
? konumsal bir yer tutucudur — değerler, ifadede göründükleri sıraya göre bağlanır. :name (ayrıca @name ve $name) ise isimli yer tutuculardır; konuma değil isme göre bağlama yaparsınız. İki üçten fazla değer varsa isimli parametreler hem okumayı hem de sırayı değiştirmeyi epey kolaylaştırır.
SQLite'ta NULL değer nasıl bağlanır?
Kullandığınız dilin null/None/nil karşılığını bind API'sine geçirmeniz yeterli — sürücü bunu otomatik olarak SQL NULL değerine çevirir. Asla 'NULL' string'ini yazmayın ve NULL kelimesini SQL metnine elle eklemeyin. Bağlamanın bütün amacı, değerleri SQL ayrıştırıcısının dışında tutmaktır.
Aynı ifadede konumsal ve isimli parametreleri karıştırabilir miyim?
SQLite buna izin verir ama yapmayın. İçinde hem ? hem :name bulunan bir ifade hem okumayı zorlaştırır hem de yanlış bağlama riskini artırır. İfade başına tek bir stil seçin — iki üçten fazla değer varsa genellikle isimli parametreler işinizi görür.